From 701eb9976cd567c231b8edd0c79926e46b9161fd Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 24 Nov 2022 14:08:31 +0100 Subject: [PATCH 001/213] Make KeyEventArgs and TextInputEventArgs public again --- src/Avalonia.Base/Input/KeyEventArgs.cs | 2 +- src/Avalonia.Base/Input/TextInputEventArgs.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs index 39c9766105..35fa549995 100644 --- a/src/Avalonia.Base/Input/KeyEventArgs.cs +++ b/src/Avalonia.Base/Input/KeyEventArgs.cs @@ -5,7 +5,7 @@ namespace Avalonia.Input { public class KeyEventArgs : RoutedEventArgs { - internal KeyEventArgs() + public KeyEventArgs() { } diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index 787bf1abd3..a027bec0c6 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs @@ -4,7 +4,7 @@ namespace Avalonia.Input { public class TextInputEventArgs : RoutedEventArgs { - internal TextInputEventArgs() + public TextInputEventArgs() { } From 8fe2c5b7cb3345867ece264aef57610d942495e6 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 1 Dec 2022 19:25:49 -0500 Subject: [PATCH 002/213] Standardize IsDefault member in CornerRadius --- src/Avalonia.Base/CornerRadius.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/CornerRadius.cs b/src/Avalonia.Base/CornerRadius.cs index 893f7c4565..67ef79f752 100644 --- a/src/Avalonia.Base/CornerRadius.cs +++ b/src/Avalonia.Base/CornerRadius.cs @@ -61,9 +61,13 @@ namespace Avalonia public double BottomLeft { get; } /// - /// Gets a value indicating whether all corner radii are set to 0. + /// Gets a value indicating whether the instance has default values (all corner radii are set to 0). /// - public bool IsEmpty => TopLeft.Equals(0) && IsUniform; + public bool IsDefault => TopLeft == 0 && TopRight == 0 && BottomLeft == 0 && BottomRight == 0; + + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; /// /// Gets a value indicating whether all corner radii are equal. @@ -79,7 +83,6 @@ namespace Avalonia { // ReSharper disable CompareOfFloatsByEqualityOperator return TopLeft == other.TopLeft && - TopRight == other.TopRight && BottomRight == other.BottomRight && BottomLeft == other.BottomLeft; From 9634a2cccf3419771ad1bbe0a35d998285075cf6 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 1 Dec 2022 19:26:19 -0500 Subject: [PATCH 003/213] Standardize IsDefault member in BoxShadow --- src/Avalonia.Base/Media/BoxShadow.cs | 11 +++++++++-- src/Avalonia.Base/Media/BoxShadows.cs | 4 ++-- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs index cc97d89cfc..a9f073df67 100644 --- a/src/Avalonia.Base/Media/BoxShadow.cs +++ b/src/Avalonia.Base/Media/BoxShadow.cs @@ -45,7 +45,14 @@ namespace Avalonia.Media } } - public bool IsEmpty => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0; + /// + /// Gets a value indicating whether the instance has default values. + /// + public bool IsDefault => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0; + + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; private readonly static char[] s_Separator = new char[] { ' ', '\t' }; @@ -82,7 +89,7 @@ namespace Avalonia.Media { var sb = StringBuilderCache.Acquire(); - if (IsEmpty) + if (IsDefault) { return "none"; } diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index ab2694389f..136f37a42b 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/src/Avalonia.Base/Media/BoxShadows.cs @@ -21,7 +21,7 @@ namespace Avalonia.Media { _first = shadow; _list = null; - Count = _first.IsEmpty ? 0 : 1; + Count = _first.IsDefault ? 0 : 1; } public BoxShadows(BoxShadow first, BoxShadow[] rest) @@ -118,7 +118,7 @@ namespace Avalonia.Media get { foreach(var boxShadow in this) - if (!boxShadow.IsEmpty && boxShadow.IsInset) + if (!boxShadow.IsDefault && boxShadow.IsInset) return true; return false; } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 6dcafdcfd7..0a1bbe558b 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -362,7 +362,7 @@ namespace Avalonia.Skia foreach (var boxShadow in boxShadows) { - if (!boxShadow.IsEmpty && !boxShadow.IsInset) + if (!boxShadow.IsDefault && !boxShadow.IsInset) { using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _currentOpacity)) { @@ -418,7 +418,7 @@ namespace Avalonia.Skia foreach (var boxShadow in boxShadows) { - if (!boxShadow.IsEmpty && boxShadow.IsInset) + if (!boxShadow.IsDefault && boxShadow.IsInset) { using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _currentOpacity)) { From 06a6284ee48c3ed716ae8999145b9efe10aa1e44 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 1 Dec 2022 19:39:38 -0500 Subject: [PATCH 004/213] Standardize IsDefault member in PixelRect --- src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs | 2 +- src/Avalonia.Base/PixelRect.cs | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs index 70f9fbf567..ac462aeb72 100644 --- a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs @@ -79,7 +79,7 @@ namespace Avalonia.Media.Imaging { if (Source is not IBitmap bmp) return Size.Empty; - if (SourceRect.IsEmpty) + if (SourceRect.IsDefault) return Source.Size; return SourceRect.Size.ToSizeWithDpi(bmp.Dpi); } diff --git a/src/Avalonia.Base/PixelRect.cs b/src/Avalonia.Base/PixelRect.cs index 855ba104ad..343bbccc9a 100644 --- a/src/Avalonia.Base/PixelRect.cs +++ b/src/Avalonia.Base/PixelRect.cs @@ -133,9 +133,13 @@ namespace Avalonia public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2)); /// - /// Gets a value that indicates whether the rectangle is empty. + /// Gets a value indicating whether the instance has default values. /// - public bool IsEmpty => Width == 0 && Height == 0; + public bool IsDefault => Width == 0 && Height == 0; + + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; /// /// Checks for equality between two s. @@ -290,11 +294,11 @@ namespace Avalonia /// The union. public PixelRect Union(PixelRect rect) { - if (IsEmpty) + if (IsDefault) { return rect; } - else if (rect.IsEmpty) + else if (rect.IsDefault) { return this; } From b8a1aaa329e10190afd31aae324c8cbf7b180f36 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 1 Dec 2022 20:49:19 -0500 Subject: [PATCH 005/213] Standardize IsDefault member in Rect --- src/Avalonia.Base/Media/FormattedText.cs | 2 +- src/Avalonia.Base/Media/ImageDrawing.cs | 2 +- src/Avalonia.Base/PixelRect.cs | 2 +- src/Avalonia.Base/Rect.cs | 14 +++++++++----- .../Composition/Server/ServerCompositionTarget.cs | 6 +++--- .../Composition/Server/ServerCompositionVisual.cs | 6 +++--- src/Avalonia.Base/Rendering/DeferredRenderer.cs | 2 +- src/Avalonia.Base/Rendering/DirtyRects.cs | 2 +- .../DataGridCheckBoxColumn.cs | 4 ++-- src/Avalonia.Controls/NativeControlHost.cs | 2 +- .../PopupPositioning/ManagedPopupPositioner.cs | 2 +- src/Avalonia.Controls/Repeater/ViewportManager.cs | 4 ++-- 12 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index 90b9755493..87dca16205 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -1383,7 +1383,7 @@ namespace Avalonia.Media } } - if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsEmpty) + if (accumulatedBounds?.PlatformImpl == null || accumulatedBounds.PlatformImpl.Bounds.IsDefault) { return null; } diff --git a/src/Avalonia.Base/Media/ImageDrawing.cs b/src/Avalonia.Base/Media/ImageDrawing.cs index 82f97b52b4..d3e5c4841b 100644 --- a/src/Avalonia.Base/Media/ImageDrawing.cs +++ b/src/Avalonia.Base/Media/ImageDrawing.cs @@ -42,7 +42,7 @@ namespace Avalonia.Media var imageSource = ImageSource; var rect = Rect; - if (imageSource is object && !rect.IsEmpty) + if (imageSource is object && !rect.IsDefault) { context.DrawImage(imageSource, rect); } diff --git a/src/Avalonia.Base/PixelRect.cs b/src/Avalonia.Base/PixelRect.cs index 343bbccc9a..409a259182 100644 --- a/src/Avalonia.Base/PixelRect.cs +++ b/src/Avalonia.Base/PixelRect.cs @@ -133,7 +133,7 @@ namespace Avalonia public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2)); /// - /// Gets a value indicating whether the instance has default values. + /// Gets a value indicating whether the instance has default values (the rectangle is empty). /// public bool IsDefault => Width == 0 && Height == 0; diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index a91b089a33..862e4caec1 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -169,12 +169,16 @@ namespace Avalonia public Point Center => new Point(_x + (_width / 2), _y + (_height / 2)); /// - /// Gets a value that indicates whether the rectangle is empty. + /// Gets a value indicating whether the instance has default values (the rectangle is empty). /// // ReSharper disable CompareOfFloatsByEqualityOperator - public bool IsEmpty => _width == 0 && _height == 0; + public bool IsDefault => _width == 0 && _height == 0; // ReSharper restore CompareOfFloatsByEqualityOperator + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; + /// /// Checks for equality between two s. /// @@ -457,7 +461,7 @@ namespace Avalonia /// public Rect Normalize() { - Rect rect = this; + Rect rect = this; if(double.IsNaN(rect.Right) || double.IsNaN(rect.Bottom) || double.IsNaN(rect.X) || double.IsNaN(rect.Y) || @@ -493,11 +497,11 @@ namespace Avalonia /// The union. public Rect Union(Rect rect) { - if (IsEmpty) + if (IsDefault) { return rect; } - else if (rect.IsEmpty) + else if (rect.IsDefault) { return this; } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 5c1ac0312c..a8fcdc41cd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -89,7 +89,7 @@ namespace Avalonia.Rendering.Composition.Server Compositor.UpdateServerTime(); - if(_dirtyRect.IsEmpty && !_redrawRequested) + if(_dirtyRect.IsDefault && !_redrawRequested) return; Revision++; @@ -117,7 +117,7 @@ namespace Avalonia.Rendering.Composition.Server _layerSize = layerSize; } - if (!_dirtyRect.IsEmpty) + if (!_dirtyRect.IsDefault) { var visualBrushHelper = new CompositorDrawingContextProxy.VisualBrushRenderer(); using (var context = _layer.CreateDrawingContext(visualBrushHelper)) @@ -179,7 +179,7 @@ namespace Avalonia.Rendering.Composition.Server public void AddDirtyRect(Rect rect) { - if(rect.IsEmpty) + if(rect.IsDefault) return; var snapped = SnapToDevicePixels(rect, Scaling); DebugEvents?.RectInvalidated(rect); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index d724e14298..5e6dcab209 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -37,7 +37,7 @@ namespace Avalonia.Rendering.Composition.Server return; currentTransformedClip = currentTransformedClip.Intersect(_combinedTransformedClipBounds); - if(currentTransformedClip.IsEmpty) + if(currentTransformedClip.IsDefault) return; Root!.RenderedVisuals++; @@ -139,7 +139,7 @@ namespace Avalonia.Rendering.Composition.Server if (ownBounds != _oldOwnContentBounds || positionChanged) { _oldOwnContentBounds = ownBounds; - if (ownBounds.IsEmpty) + if (ownBounds.IsDefault) TransformedOwnContentBounds = default; else TransformedOwnContentBounds = @@ -163,7 +163,7 @@ namespace Avalonia.Rendering.Composition.Server EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1); IsVisibleInFrame = _parent?.IsVisibleInFrame != false && Visible && EffectiveOpacity > 0.04 && !_isBackface && - !_combinedTransformedClipBounds.IsEmpty; + !_combinedTransformedClipBounds.IsDefault; if (wasVisible != IsVisibleInFrame || positionChanged) { diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs index 971702269c..33820a0eac 100644 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Base/Rendering/DeferredRenderer.cs @@ -420,7 +420,7 @@ namespace Avalonia.Rendering { clipBounds = node.ClipBounds.Intersect(clipBounds); - if (!clipBounds.IsEmpty && node.Opacity > 0) + if (!clipBounds.IsDefault && node.Opacity > 0) { var isLayerRoot = node.Visual == layer; diff --git a/src/Avalonia.Base/Rendering/DirtyRects.cs b/src/Avalonia.Base/Rendering/DirtyRects.cs index f4b6a6b6ce..e4238f8308 100644 --- a/src/Avalonia.Base/Rendering/DirtyRects.cs +++ b/src/Avalonia.Base/Rendering/DirtyRects.cs @@ -27,7 +27,7 @@ namespace Avalonia.Rendering /// public void Add(Rect rect) { - if (!rect.IsEmpty) + if (!rect.IsDefault) { for (var i = 0; i < _rects.Count; ++i) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index 6b1796e50b..c308312398 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -192,14 +192,14 @@ namespace Avalonia.Controls void OnLayoutUpdated(object sender, EventArgs e) { - if(!editingCheckBox.Bounds.IsEmpty) + if(!editingCheckBox.Bounds.IsDefault) { editingCheckBox.LayoutUpdated -= OnLayoutUpdated; ProcessPointerArgs(); } } - if(editingCheckBox.Bounds.IsEmpty) + if(editingCheckBox.Bounds.IsDefault) { editingCheckBox.LayoutUpdated += OnLayoutUpdated; } diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index bcf0866129..18dc1b1264 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -145,7 +145,7 @@ namespace Avalonia.Controls if (IsEffectivelyVisible && bounds.HasValue) { - if (bounds.Value.IsEmpty) + if (bounds.Value.IsDefault) return false; _attachment?.ShowInBounds(bounds.Value); } diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs index a80a60350e..62e5f37273 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs @@ -112,7 +112,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning ?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry)) ?? screens.FirstOrDefault(); - if (targetScreen != null && targetScreen.WorkingArea.IsEmpty) + if (targetScreen != null && targetScreen.WorkingArea.IsDefault) { return targetScreen.Bounds; } diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index e24ed37f1e..10e3dd57a0 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -465,7 +465,7 @@ namespace Avalonia.Controls _pendingViewportShift = default; _unshiftableShift = default; - if (_visibleWindow.IsEmpty) + if (_visibleWindow.IsDefault) { // We got cleared. _layoutExtent = default; @@ -551,7 +551,7 @@ namespace Avalonia.Controls private void TryInvalidateMeasure() { // Don't invalidate measure if we have an invalid window. - if (!_visibleWindow.IsEmpty) + if (!_visibleWindow.IsDefault) { // We invalidate measure instead of just invalidating arrange because // we don't invalidate measure in UpdateViewport if the view is changing to From acfc66eeb196bf5008f56e7f08d70a6dbfde9443 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 1 Dec 2022 20:50:46 -0500 Subject: [PATCH 006/213] Add DirtyRects.IsEmpty comment IsEmpty is allowed to stay here because it is an internal API. Empty also makes sense when working with collections. --- src/Avalonia.Base/Rendering/DirtyRects.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Avalonia.Base/Rendering/DirtyRects.cs b/src/Avalonia.Base/Rendering/DirtyRects.cs index e4238f8308..723fe400b3 100644 --- a/src/Avalonia.Base/Rendering/DirtyRects.cs +++ b/src/Avalonia.Base/Rendering/DirtyRects.cs @@ -10,6 +10,9 @@ namespace Avalonia.Rendering { private List _rects = new List(); + /// + /// Gets a value indicating whether the collection of dirty rectangles is empty. + /// public bool IsEmpty => _rects.Count == 0; /// From 210390243f9f2b346ac168b4598465860f78b0c6 Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 1 Dec 2022 20:54:21 -0500 Subject: [PATCH 007/213] Standardize IsDefault member in Thickness --- src/Avalonia.Base/Thickness.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Thickness.cs b/src/Avalonia.Base/Thickness.cs index da3a98088f..beaf85c9d3 100644 --- a/src/Avalonia.Base/Thickness.cs +++ b/src/Avalonia.Base/Thickness.cs @@ -97,10 +97,9 @@ namespace Avalonia /// public double Bottom => _bottom; - /// - /// Gets a value indicating whether all sides are set to 0. - /// - public bool IsEmpty => Left.Equals(0) && IsUniform; + /// + [Obsolete("Use IsDefault instead.")] + public bool IsEmpty => IsDefault; /// /// Gets a value indicating whether all sides are equal. @@ -292,15 +291,13 @@ namespace Avalonia left = this._left; top = this._top; right = this._right; - bottom = this._bottom; + bottom = this._bottom; } /// - /// Gets a value indicating whether the left, top, right and bottom thickness values are zero. + /// Gets a value indicating whether the instance has default values + /// (the left, top, right and bottom values are zero). /// - public bool IsDefault - { - get { return (_left == 0) && (_top == 0) && (_right == 0) && (_bottom == 0); } - } + public bool IsDefault => (_left == 0) && (_top == 0) && (_right == 0) && (_bottom == 0); } } From 5a743ad4c85067a22ee3d0f03878364ec6c61bdc Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 3 Dec 2022 10:20:10 -0500 Subject: [PATCH 008/213] Standardize Default member in PixelRect --- samples/ControlCatalog/Pages/ImagePage.xaml.cs | 2 +- src/Avalonia.Base/PixelRect.cs | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs index 511b01c7ac..00878dc5a8 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs +++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs @@ -70,7 +70,7 @@ namespace ControlCatalog.Pages 3 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, 0), cropSize), 4 => new PixelRect(new PixelPoint(0, bitmapHeight - cropSize.Height), cropSize), 5 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, bitmapHeight - cropSize.Height), cropSize), - _ => PixelRect.Empty + _ => PixelRect.Default }; } diff --git a/src/Avalonia.Base/PixelRect.cs b/src/Avalonia.Base/PixelRect.cs index 409a259182..330edbd872 100644 --- a/src/Avalonia.Base/PixelRect.cs +++ b/src/Avalonia.Base/PixelRect.cs @@ -10,9 +10,13 @@ namespace Avalonia public readonly struct PixelRect : IEquatable { /// - /// An empty rectangle. + /// A shared default instance representing an empty rectangle. /// - public static readonly PixelRect Empty = default; + public static readonly PixelRect Default = default; + + /// + [Obsolete("Use Default instead.")] + public static readonly PixelRect Empty = Default; /// /// Initializes a new instance of the structure. @@ -261,7 +265,7 @@ namespace Avalonia } else { - return Empty; + return Default; } } From 509ebddcc4fc8408aa5799367e2a40a1c1586c0f Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 3 Dec 2022 10:25:13 -0500 Subject: [PATCH 009/213] Standardize Default member in Rect --- src/Avalonia.Base/Media/Geometry.cs | 4 ++-- src/Avalonia.Base/Media/GeometryDrawing.cs | 2 +- src/Avalonia.Base/Media/GlyphRunDrawing.cs | 2 +- .../Media/TextFormatting/TextLineImpl.cs | 4 ++-- src/Avalonia.Base/Rect.cs | 12 ++++++++---- .../Server/ServerCompositionDrawListVisual.cs | 2 +- .../Composition/Server/ServerCompositionTarget.cs | 2 +- .../Composition/Server/ServerCompositionVisual.cs | 2 +- src/Avalonia.Base/Rendering/ImmediateRenderer.cs | 2 +- .../Rendering/SceneGraph/BitmapBlendModeNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs | 2 +- .../Rendering/SceneGraph/GeometryClipNode.cs | 2 +- .../Rendering/SceneGraph/OpacityMaskNode.cs | 4 ++-- .../Rendering/SceneGraph/OpacityNode.cs | 2 +- .../Rendering/SceneGraph/SceneBuilder.cs | 6 +++--- .../Primitives/DataGridCellsPresenter.cs | 2 +- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 2 +- .../HeadlessPlatformRenderInterface.cs | 2 +- .../Avalonia.Browser/WebEmbeddableControlRoot.cs | 2 +- src/Skia/Avalonia.Skia/GeometryImpl.cs | 2 +- src/Skia/Avalonia.Skia/StreamGeometryImpl.cs | 2 +- tests/Avalonia.Base.UnitTests/RectTests.cs | 2 +- .../Rendering/DeferredRendererTests_HitTesting.cs | 4 ++-- .../Rendering/SceneGraph/DrawOperationTests.cs | 4 ++-- .../ItemsPresenterTests_Virtualization_Simple.cs | 2 +- .../Shapes/RectangleTests.cs | 2 +- 26 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index c31a6699c2..7d56140f22 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -30,7 +30,7 @@ namespace Avalonia.Media /// /// Gets the geometry's bounding rectangle. /// - public Rect Bounds => PlatformImpl?.Bounds ?? Rect.Empty; + public Rect Bounds => PlatformImpl?.Bounds ?? Rect.Default; /// /// Gets the platform-specific implementation of the geometry. @@ -84,7 +84,7 @@ namespace Avalonia.Media /// /// The stroke thickness. /// The bounding rectangle. - public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty; + public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Default; /// /// Indicates whether the geometry's fill contains the specified point. diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs index 7df7d25954..e9cbcecabe 100644 --- a/src/Avalonia.Base/Media/GeometryDrawing.cs +++ b/src/Avalonia.Base/Media/GeometryDrawing.cs @@ -69,7 +69,7 @@ namespace Avalonia.Media public override Rect GetBounds() { IPen pen = Pen ?? s_boundsPen; - return Geometry?.GetRenderBounds(pen) ?? Rect.Empty; + return Geometry?.GetRenderBounds(pen) ?? Rect.Default; } } } diff --git a/src/Avalonia.Base/Media/GlyphRunDrawing.cs b/src/Avalonia.Base/Media/GlyphRunDrawing.cs index 7e0d5c3c81..eab2d99387 100644 --- a/src/Avalonia.Base/Media/GlyphRunDrawing.cs +++ b/src/Avalonia.Base/Media/GlyphRunDrawing.cs @@ -32,7 +32,7 @@ public override Rect GetBounds() { - return GlyphRun != null ? new Rect(GlyphRun.Size) : Rect.Empty; + return GlyphRun != null ? new Rect(GlyphRun.Size) : Rect.Default; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 96f88d1f44..8f56e1d4c9 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -528,7 +528,7 @@ namespace Avalonia.Media.TextFormatting var startX = Start; double currentWidth = 0; - var currentRect = Rect.Empty; + var currentRect = Rect.Default; TextRunBounds lastRunBounds = default; @@ -762,7 +762,7 @@ namespace Avalonia.Media.TextFormatting var startX = WidthIncludingTrailingWhitespace; double currentWidth = 0; - var currentRect = Rect.Empty; + var currentRect = Rect.Default; for (var index = TextRuns.Count - 1; index >= 0; index--) { diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index 862e4caec1..50b776d14f 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -16,9 +16,13 @@ namespace Avalonia } /// - /// An empty rectangle. + /// A shared default instance representing an empty rectangle. /// - public static readonly Rect Empty = default(Rect); + public static readonly Rect Default = default; + + /// + [Obsolete("Use Default instead.")] + public static readonly Rect Empty = Default; /// /// The X position. @@ -394,7 +398,7 @@ namespace Avalonia } else { - return Empty; + return Default; } } @@ -467,7 +471,7 @@ namespace Avalonia double.IsNaN(rect.X) || double.IsNaN(rect.Y) || double.IsNaN(Height) || double.IsNaN(Width)) { - return Rect.Empty; + return Rect.Default; } if (rect.Width < 0) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 6cbd2797f9..95ed8e27c1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -37,7 +37,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua { if (_contentBounds == null) { - var rect = Rect.Empty; + var rect = Rect.Default; if(_renderCommands!=null) foreach (var cmd in _renderCommands) rect = rect.Union(cmd.Item.Bounds); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index c9517d01f8..c378f4981a 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.Composition.Server } RenderedVisuals = 0; - _dirtyRect = Rect.Empty; + _dirtyRect = Rect.Default; } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 5e6dcab209..e6943211e4 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -192,7 +192,7 @@ namespace Avalonia.Rendering.Composition.Server void AddDirtyRect(Rect rc) { - if(rc == Rect.Empty) + if(rc == Rect.Default) return; Root?.AddDirtyRect(rc); } diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 9e1582ed43..98a224181c 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -127,7 +127,7 @@ namespace Avalonia.Rendering /// public void AddDirty(Visual visual) { - if (visual.Bounds != Rect.Empty) + if (visual.Bounds != Rect.Default) { var m = visual.TransformToVisual(_root); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs index 98e89f6549..6739e5b287 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs @@ -27,7 +27,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => Rect.Default; /// /// Gets the BitmapBlend to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs index 90430bed02..9de18c068c 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs @@ -40,7 +40,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => Rect.Default; /// /// Gets the clip to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs index 667b66420b..e8690ac6f8 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs @@ -28,7 +28,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => Rect.Default; /// /// Gets the clip to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs index 5fd200ddff..521fafe816 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The bounds of the mask. /// Auxiliary data required to draw the brush. public OpacityMaskNode(IBrush mask, Rect bounds, IDisposable? aux = null) - : base(Rect.Empty, Matrix.Identity, aux) + : base(Rect.Default, Matrix.Identity, aux) { Mask = mask.ToImmutable(); MaskBounds = bounds; @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph /// opacity mask pop. /// public OpacityMaskNode() - : base(Rect.Empty, Matrix.Identity, null) + : base(Rect.Default, Matrix.Identity, null) { } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs index 8fc630588f..af8b710191 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs @@ -26,7 +26,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Empty; + public Rect Bounds => Rect.Default; /// /// Gets the opacity to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs index d54bd3fad8..863662c93f 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs @@ -331,8 +331,8 @@ namespace Avalonia.Rendering.SceneGraph scene.Size = newSize; - Rect horizontalDirtyRect = Rect.Empty; - Rect verticalDirtyRect = Rect.Empty; + Rect horizontalDirtyRect = Rect.Default; + Rect verticalDirtyRect = Rect.Default; if (newSize.Width > oldSize.Width) { @@ -429,7 +429,7 @@ namespace Avalonia.Rendering.SceneGraph else { layer.OpacityMask = null; - layer.OpacityMaskRect = Rect.Empty; + layer.OpacityMaskRect = Rect.Default; } layer.GeometryClip = node.HasAncestorGeometryClip ? diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs index 38d559a031..0e552af2ba 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs @@ -161,7 +161,7 @@ namespace Avalonia.Controls.Primitives { // Clip RectangleGeometry rg = new RectangleGeometry(); - rg.Rect = Rect.Empty; + rg.Rect = Rect.Default; cell.Clip = rg; } } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 3ff248f0be..2fad480bba 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -457,7 +457,7 @@ namespace Avalonia.Controls.Primitives PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; } - var trgtBnds = Target?.Bounds ?? Rect.Empty; + var trgtBnds = Target?.Bounds ?? Rect.Default; switch (Placement) { diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 7abc0ca131..c39f37aded 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -212,7 +212,7 @@ namespace Avalonia.Headless class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl { - public HeadlessStreamingGeometryStub() : base(Rect.Empty) + public HeadlessStreamingGeometryStub() : base(Rect.Default) { } diff --git a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs index e389ee98ea..8659b8f0d9 100644 --- a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs +++ b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs @@ -18,7 +18,7 @@ namespace Avalonia.Browser _onFirstRender = onFirstRender; } - public Rect Bounds => Rect.Empty; + public Rect Bounds => Rect.Default; public bool HasRendered => _hasRendered; diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 4037cc4a35..6ff6354b4f 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -244,7 +244,7 @@ namespace Avalonia.Skia public void Invalidate() { CachedStrokePath?.Dispose(); - CachedGeometryRenderBounds = Rect.Empty; + CachedGeometryRenderBounds = Rect.Default; _cachedStrokeWidth = default(float); } } diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index 86450690e6..6e0d42b478 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -34,7 +34,7 @@ namespace Avalonia.Skia /// /// Initializes a new instance of the class. /// - public StreamGeometryImpl() : this(CreateEmptyPath(), Rect.Empty) + public StreamGeometryImpl() : this(CreateEmptyPath(), Rect.Default) { } diff --git a/tests/Avalonia.Base.UnitTests/RectTests.cs b/tests/Avalonia.Base.UnitTests/RectTests.cs index 95a438b287..15265f1fcd 100644 --- a/tests/Avalonia.Base.UnitTests/RectTests.cs +++ b/tests/Avalonia.Base.UnitTests/RectTests.cs @@ -52,7 +52,7 @@ namespace Avalonia.Base.UnitTests double.PositiveInfinity, double.PositiveInfinity) .Normalize(); - Assert.Equal(Rect.Empty, result); + Assert.Equal(Rect.Default, result); } } } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index 2cf42d9604..ce4fff77e9 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -403,7 +403,7 @@ namespace Avalonia.Base.UnitTests.Rendering root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(Rect.Empty); + root.Renderer.Paint(Rect.Default); var result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item1, result); @@ -419,7 +419,7 @@ namespace Avalonia.Base.UnitTests.Rendering container.InvalidateArrange(); container.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(Rect.Empty); + root.Renderer.Paint(Rect.Default); result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item2, result); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 2fac968206..4a347762e1 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -13,9 +13,9 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph [Fact] public void Empty_Bounds_Remain_Empty() { - var target = new TestDrawOperation(Rect.Empty, Matrix.Identity, null); + var target = new TestDrawOperation(Rect.Default, Matrix.Identity, null); - Assert.Equal(Rect.Empty, target.Bounds); + Assert.Equal(Rect.Default, target.Bounds); } [Theory] diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 08fd777ac6..d40f4f8cd0 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -570,7 +570,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var items = (IList)target.Items; target.ApplyTemplate(); target.Measure(Size.Empty); - target.Arrange(Rect.Empty); + target.Arrange(Rect.Default); // Check for issue #591: this should not throw. target.ScrollIntoView(0); diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs index 8d8ce10d4c..6a6d3975a7 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Shapes target.Measure(new Size(100, 100)); var geometry = Assert.IsType(target.RenderedGeometry); - Assert.Equal(Rect.Empty, geometry.Rect); + Assert.Equal(Rect.Default, geometry.Rect); } [Fact] From a799f722420080991e9732bf2ac17378fbc7a327 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 5 Dec 2022 07:33:08 -0500 Subject: [PATCH 010/213] Remove PixelRect.Default --- samples/ControlCatalog/Pages/ImagePage.xaml.cs | 2 +- src/Avalonia.Base/PixelRect.cs | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/samples/ControlCatalog/Pages/ImagePage.xaml.cs b/samples/ControlCatalog/Pages/ImagePage.xaml.cs index 00878dc5a8..5b3169a1b0 100644 --- a/samples/ControlCatalog/Pages/ImagePage.xaml.cs +++ b/samples/ControlCatalog/Pages/ImagePage.xaml.cs @@ -70,7 +70,7 @@ namespace ControlCatalog.Pages 3 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, 0), cropSize), 4 => new PixelRect(new PixelPoint(0, bitmapHeight - cropSize.Height), cropSize), 5 => new PixelRect(new PixelPoint(bitmapWidth - cropSize.Width, bitmapHeight - cropSize.Height), cropSize), - _ => PixelRect.Default + _ => default }; } diff --git a/src/Avalonia.Base/PixelRect.cs b/src/Avalonia.Base/PixelRect.cs index 330edbd872..469f33e7fd 100644 --- a/src/Avalonia.Base/PixelRect.cs +++ b/src/Avalonia.Base/PixelRect.cs @@ -10,13 +10,10 @@ namespace Avalonia public readonly struct PixelRect : IEquatable { /// - /// A shared default instance representing an empty rectangle. + /// An empty rectangle. /// - public static readonly PixelRect Default = default; - - /// - [Obsolete("Use Default instead.")] - public static readonly PixelRect Empty = Default; + [Obsolete("Use the default keyword instead.")] + public static readonly PixelRect Empty = default; /// /// Initializes a new instance of the structure. @@ -265,7 +262,7 @@ namespace Avalonia } else { - return Default; + return default; } } From 3fc6a51571d65dfcdeb21a20df1c9667b8c1164b Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 5 Dec 2022 07:43:51 -0500 Subject: [PATCH 011/213] Remove Rect.Default --- src/Avalonia.Base/Media/Geometry.cs | 4 ++-- src/Avalonia.Base/Media/GeometryDrawing.cs | 2 +- src/Avalonia.Base/Media/GlyphRunDrawing.cs | 2 +- .../Media/TextFormatting/TextLineImpl.cs | 4 ++-- src/Avalonia.Base/Rect.cs | 13 +++++-------- .../Server/ServerCompositionDrawListVisual.cs | 2 +- .../Composition/Server/ServerCompositionTarget.cs | 2 +- .../Composition/Server/ServerCompositionVisual.cs | 2 +- src/Avalonia.Base/Rendering/ImmediateRenderer.cs | 2 +- .../Rendering/SceneGraph/BitmapBlendModeNode.cs | 2 +- src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs | 2 +- .../Rendering/SceneGraph/GeometryClipNode.cs | 2 +- .../Rendering/SceneGraph/OpacityMaskNode.cs | 4 ++-- .../Rendering/SceneGraph/OpacityNode.cs | 2 +- .../Rendering/SceneGraph/SceneBuilder.cs | 6 +++--- .../Primitives/DataGridCellsPresenter.cs | 2 +- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 2 +- .../HeadlessPlatformRenderInterface.cs | 2 +- .../Avalonia.Browser/WebEmbeddableControlRoot.cs | 2 +- src/Skia/Avalonia.Skia/GeometryImpl.cs | 2 +- src/Skia/Avalonia.Skia/StreamGeometryImpl.cs | 2 +- tests/Avalonia.Base.UnitTests/RectTests.cs | 2 +- .../Rendering/DeferredRendererTests_HitTesting.cs | 4 ++-- .../Rendering/SceneGraph/DrawOperationTests.cs | 4 ++-- .../ItemsPresenterTests_Virtualization_Simple.cs | 2 +- .../Shapes/RectangleTests.cs | 2 +- 26 files changed, 37 insertions(+), 40 deletions(-) diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index 7d56140f22..2019f54c70 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -30,7 +30,7 @@ namespace Avalonia.Media /// /// Gets the geometry's bounding rectangle. /// - public Rect Bounds => PlatformImpl?.Bounds ?? Rect.Default; + public Rect Bounds => PlatformImpl?.Bounds ?? default; /// /// Gets the platform-specific implementation of the geometry. @@ -84,7 +84,7 @@ namespace Avalonia.Media /// /// The stroke thickness. /// The bounding rectangle. - public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Default; + public Rect GetRenderBounds(IPen pen) => PlatformImpl?.GetRenderBounds(pen) ?? default; /// /// Indicates whether the geometry's fill contains the specified point. diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs index e9cbcecabe..26cc2c3cab 100644 --- a/src/Avalonia.Base/Media/GeometryDrawing.cs +++ b/src/Avalonia.Base/Media/GeometryDrawing.cs @@ -69,7 +69,7 @@ namespace Avalonia.Media public override Rect GetBounds() { IPen pen = Pen ?? s_boundsPen; - return Geometry?.GetRenderBounds(pen) ?? Rect.Default; + return Geometry?.GetRenderBounds(pen) ?? default; } } } diff --git a/src/Avalonia.Base/Media/GlyphRunDrawing.cs b/src/Avalonia.Base/Media/GlyphRunDrawing.cs index eab2d99387..242b9913fa 100644 --- a/src/Avalonia.Base/Media/GlyphRunDrawing.cs +++ b/src/Avalonia.Base/Media/GlyphRunDrawing.cs @@ -32,7 +32,7 @@ public override Rect GetBounds() { - return GlyphRun != null ? new Rect(GlyphRun.Size) : Rect.Default; + return GlyphRun != null ? new Rect(GlyphRun.Size) : default; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 8f56e1d4c9..5312807a00 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -528,7 +528,7 @@ namespace Avalonia.Media.TextFormatting var startX = Start; double currentWidth = 0; - var currentRect = Rect.Default; + var currentRect = default(Rect); TextRunBounds lastRunBounds = default; @@ -762,7 +762,7 @@ namespace Avalonia.Media.TextFormatting var startX = WidthIncludingTrailingWhitespace; double currentWidth = 0; - var currentRect = Rect.Default; + var currentRect = default(Rect); for (var index = TextRuns.Count - 1; index >= 0; index--) { diff --git a/src/Avalonia.Base/Rect.cs b/src/Avalonia.Base/Rect.cs index 50b776d14f..831ab28adc 100644 --- a/src/Avalonia.Base/Rect.cs +++ b/src/Avalonia.Base/Rect.cs @@ -16,13 +16,10 @@ namespace Avalonia } /// - /// A shared default instance representing an empty rectangle. + /// An empty rectangle. /// - public static readonly Rect Default = default; - - /// - [Obsolete("Use Default instead.")] - public static readonly Rect Empty = Default; + [Obsolete("Use the default keyword instead.")] + public static readonly Rect Empty = default; /// /// The X position. @@ -398,7 +395,7 @@ namespace Avalonia } else { - return Default; + return default; } } @@ -471,7 +468,7 @@ namespace Avalonia double.IsNaN(rect.X) || double.IsNaN(rect.Y) || double.IsNaN(Height) || double.IsNaN(Width)) { - return Rect.Default; + return default; } if (rect.Width < 0) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 95ed8e27c1..8dc088fed1 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -37,7 +37,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua { if (_contentBounds == null) { - var rect = Rect.Default; + var rect = default(Rect); if(_renderCommands!=null) foreach (var cmd in _renderCommands) rect = rect.Union(cmd.Item.Bounds); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index c378f4981a..f68994c812 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -160,7 +160,7 @@ namespace Avalonia.Rendering.Composition.Server } RenderedVisuals = 0; - _dirtyRect = Rect.Default; + _dirtyRect = default; } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index e6943211e4..387998f8d6 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -192,7 +192,7 @@ namespace Avalonia.Rendering.Composition.Server void AddDirtyRect(Rect rc) { - if(rc == Rect.Default) + if(rc == default) return; Root?.AddDirtyRect(rc); } diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 98a224181c..989d4eb4d7 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -127,7 +127,7 @@ namespace Avalonia.Rendering /// public void AddDirty(Visual visual) { - if (visual.Bounds != Rect.Default) + if (!visual.Bounds.IsDefault) { var m = visual.TransformToVisual(_root); diff --git a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs index 6739e5b287..b1190a159b 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/BitmapBlendModeNode.cs @@ -27,7 +27,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Default; + public Rect Bounds => default; /// /// Gets the BitmapBlend to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs index 9de18c068c..e1bfaa4aa3 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ClipNode.cs @@ -40,7 +40,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Default; + public Rect Bounds => default; /// /// Gets the clip to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs index e8690ac6f8..842edf2bcb 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryClipNode.cs @@ -28,7 +28,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Default; + public Rect Bounds => default; /// /// Gets the clip to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs index 521fafe816..3ecc07fa54 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The bounds of the mask. /// Auxiliary data required to draw the brush. public OpacityMaskNode(IBrush mask, Rect bounds, IDisposable? aux = null) - : base(Rect.Default, Matrix.Identity, aux) + : base(default, Matrix.Identity, aux) { Mask = mask.ToImmutable(); MaskBounds = bounds; @@ -30,7 +30,7 @@ namespace Avalonia.Rendering.SceneGraph /// opacity mask pop. /// public OpacityMaskNode() - : base(Rect.Default, Matrix.Identity, null) + : base(default, Matrix.Identity, null) { } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs index af8b710191..e41e639067 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityNode.cs @@ -26,7 +26,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public Rect Bounds => Rect.Default; + public Rect Bounds => default; /// /// Gets the opacity to be pushed or null if the operation represents a pop. diff --git a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs index 863662c93f..55ff772772 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/SceneBuilder.cs @@ -331,8 +331,8 @@ namespace Avalonia.Rendering.SceneGraph scene.Size = newSize; - Rect horizontalDirtyRect = Rect.Default; - Rect verticalDirtyRect = Rect.Default; + Rect horizontalDirtyRect = default; + Rect verticalDirtyRect = default; if (newSize.Width > oldSize.Width) { @@ -429,7 +429,7 @@ namespace Avalonia.Rendering.SceneGraph else { layer.OpacityMask = null; - layer.OpacityMaskRect = Rect.Default; + layer.OpacityMaskRect = default; } layer.GeometryClip = node.HasAncestorGeometryClip ? diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs index 0e552af2ba..06a77f0894 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridCellsPresenter.cs @@ -161,7 +161,7 @@ namespace Avalonia.Controls.Primitives { // Clip RectangleGeometry rg = new RectangleGeometry(); - rg.Rect = Rect.Default; + rg.Rect = default; cell.Clip = rg; } } diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 2fad480bba..7f7600e877 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -457,7 +457,7 @@ namespace Avalonia.Controls.Primitives PopupPositioning.PopupPositionerConstraintAdjustment.SlideY; } - var trgtBnds = Target?.Bounds ?? Rect.Default; + var trgtBnds = Target?.Bounds ?? default; switch (Placement) { diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index c39f37aded..d95ce3fe85 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -212,7 +212,7 @@ namespace Avalonia.Headless class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl { - public HeadlessStreamingGeometryStub() : base(Rect.Default) + public HeadlessStreamingGeometryStub() : base(default) { } diff --git a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs index 8659b8f0d9..df1a24fa0f 100644 --- a/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs +++ b/src/Browser/Avalonia.Browser/WebEmbeddableControlRoot.cs @@ -18,7 +18,7 @@ namespace Avalonia.Browser _onFirstRender = onFirstRender; } - public Rect Bounds => Rect.Default; + public Rect Bounds => default; public bool HasRendered => _hasRendered; diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 6ff6354b4f..1141763097 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -244,7 +244,7 @@ namespace Avalonia.Skia public void Invalidate() { CachedStrokePath?.Dispose(); - CachedGeometryRenderBounds = Rect.Default; + CachedGeometryRenderBounds = default; _cachedStrokeWidth = default(float); } } diff --git a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs index 6e0d42b478..df847d2224 100644 --- a/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/StreamGeometryImpl.cs @@ -34,7 +34,7 @@ namespace Avalonia.Skia /// /// Initializes a new instance of the class. /// - public StreamGeometryImpl() : this(CreateEmptyPath(), Rect.Default) + public StreamGeometryImpl() : this(CreateEmptyPath(), default) { } diff --git a/tests/Avalonia.Base.UnitTests/RectTests.cs b/tests/Avalonia.Base.UnitTests/RectTests.cs index 15265f1fcd..c44b328ed5 100644 --- a/tests/Avalonia.Base.UnitTests/RectTests.cs +++ b/tests/Avalonia.Base.UnitTests/RectTests.cs @@ -52,7 +52,7 @@ namespace Avalonia.Base.UnitTests double.PositiveInfinity, double.PositiveInfinity) .Normalize(); - Assert.Equal(Rect.Default, result); + Assert.Equal(default, result); } } } diff --git a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs index ce4fff77e9..c164012446 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs @@ -403,7 +403,7 @@ namespace Avalonia.Base.UnitTests.Rendering root.Measure(Size.Infinity); root.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(Rect.Default); + root.Renderer.Paint(default); var result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item1, result); @@ -419,7 +419,7 @@ namespace Avalonia.Base.UnitTests.Rendering container.InvalidateArrange(); container.Arrange(new Rect(container.DesiredSize)); - root.Renderer.Paint(Rect.Default); + root.Renderer.Paint(default); result = root.Renderer.HitTest(new Point(50, 150), root, null).First(); Assert.Equal(item2, result); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 4a347762e1..07d2d672ae 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -13,9 +13,9 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph [Fact] public void Empty_Bounds_Remain_Empty() { - var target = new TestDrawOperation(Rect.Default, Matrix.Identity, null); + var target = new TestDrawOperation(default, Matrix.Identity, null); - Assert.Equal(Rect.Default, target.Bounds); + Assert.Equal(default, target.Bounds); } [Theory] diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index d40f4f8cd0..a2aba84c34 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -570,7 +570,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var items = (IList)target.Items; target.ApplyTemplate(); target.Measure(Size.Empty); - target.Arrange(Rect.Default); + target.Arrange(default); // Check for issue #591: this should not throw. target.ScrollIntoView(0); diff --git a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs index 6a6d3975a7..067d709ae1 100644 --- a/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Shapes target.Measure(new Size(100, 100)); var geometry = Assert.IsType(target.RenderedGeometry); - Assert.Equal(Rect.Default, geometry.Rect); + Assert.Equal(default, geometry.Rect); } [Fact] From 932d52dcf3151f8d5a248eac4b0001b93584f826 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 5 Dec 2022 07:52:00 -0500 Subject: [PATCH 012/213] Standardize Default members in Size --- src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs | 2 +- .../Composition/Server/DrawingContextProxy.cs | 2 +- src/Avalonia.Base/Rendering/DeferredRenderer.cs | 2 +- src/Avalonia.Base/Rendering/ImmediateRenderer.cs | 2 +- src/Avalonia.Base/Size.cs | 8 +++----- src/Avalonia.Controls.DataGrid/DataGrid.cs | 4 ++-- .../Primitives/DataGridColumnHeadersPresenter.cs | 2 +- .../Primitives/DataGridDetailsPresenter.cs | 2 +- src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 2 +- src/Avalonia.Controls/LayoutTransformControl.cs | 10 +++++----- src/Avalonia.Controls/Presenters/ItemsPresenter.cs | 6 +++--- src/Avalonia.Controls/Shapes/Shape.cs | 2 +- .../ItemsPresenterTests_Virtualization_Simple.cs | 2 +- 13 files changed, 22 insertions(+), 24 deletions(-) diff --git a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs index ac462aeb72..525a543b70 100644 --- a/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/CroppedBitmap.cs @@ -78,7 +78,7 @@ namespace Avalonia.Media.Imaging get { if (Source is not IBitmap bmp) - return Size.Empty; + return default; if (SourceRect.IsDefault) return Source.Size; return SourceRect.Size.ToSizeWithDpi(bmp.Dpi); diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 03859d241f..e6bbba6ec0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -163,7 +163,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont public CompositionDrawList? VisualBrushDrawList { get; set; } public Size GetRenderTargetSize(IVisualBrush brush) { - return VisualBrushDrawList?.Size ?? Size.Empty; + return VisualBrushDrawList?.Size ?? default; } public void RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs index a70a9ccbdf..4ad7c11d6d 100644 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Base/Rendering/DeferredRenderer.cs @@ -277,7 +277,7 @@ namespace Avalonia.Rendering /// Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) { - return TryGetChildScene(_currentDraw)?.Size ?? Size.Empty; + return TryGetChildScene(_currentDraw)?.Size ?? default; } /// diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 989d4eb4d7..4e9e7a0615 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -189,7 +189,7 @@ namespace Avalonia.Rendering Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush) { (brush.Visual as IVisualBrushInitialize)?.EnsureInitialized(); - return brush.Visual?.Bounds.Size ?? Size.Empty; + return brush.Visual?.Bounds.Size ?? default; } /// diff --git a/src/Avalonia.Base/Size.cs b/src/Avalonia.Base/Size.cs index 5f20206200..aec237afae 100644 --- a/src/Avalonia.Base/Size.cs +++ b/src/Avalonia.Base/Size.cs @@ -28,8 +28,9 @@ namespace Avalonia public static readonly Size Infinity = new Size(double.PositiveInfinity, double.PositiveInfinity); /// - /// A size representing zero + /// A size representing zero. /// + [Obsolete("Use the default keyword instead.")] public static readonly Size Empty = new Size(0, 0); /// @@ -309,9 +310,6 @@ namespace Avalonia /// /// Gets a value indicating whether the Width and Height values are zero. /// - public bool IsDefault - { - get { return (_width == 0) && (_height == 0); } - } + public bool IsDefault => (_width == 0) && (_height == 0); } } diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 454678c64b..68799915dc 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -1124,7 +1124,7 @@ namespace Avalonia.Controls EnsureColumnHeadersVisibility(); if (!newValueCols) { - _columnHeadersPresenter.Measure(Size.Empty); + _columnHeadersPresenter.Measure(default(Size)); } else { @@ -1165,7 +1165,7 @@ namespace Avalonia.Controls _topLeftCornerHeader.IsVisible = newValueRows && newValueCols; if (_topLeftCornerHeader.IsVisible) { - _topLeftCornerHeader.Measure(Size.Empty); + _topLeftCornerHeader.Measure(default(Size)); } } diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs index b34f52f47d..f9b84793c6 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridColumnHeadersPresenter.cs @@ -305,7 +305,7 @@ namespace Avalonia.Controls.Primitives } if (!OwningGrid.AreColumnHeadersVisible) { - return Size.Empty; + return default; } double height = OwningGrid.ColumnHeaderHeight; bool autoSizeHeight; diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs index 543485b311..07e7708003 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridDetailsPresenter.cs @@ -112,7 +112,7 @@ namespace Avalonia.Controls.Primitives { if (OwningGrid == null || Children.Count == 0) { - return Size.Empty; + return default; } double desiredWidth = OwningGrid.AreRowDetailsFrozen ? diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 7f7600e877..8455495830 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -435,7 +435,7 @@ namespace Avalonia.Controls.Primitives { Size sz; // Popup.Child can't be null here, it was set in ShowAtCore. - if (Popup.Child!.DesiredSize == Size.Empty) + if (Popup.Child!.DesiredSize.IsDefault) { // Popup may not have been shown yet. Measure content sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness()); diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 5668b79e81..766a712c88 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -91,7 +91,7 @@ namespace Avalonia.Controls arrangedsize = TransformRoot.Bounds.Size; // This is the first opportunity under Silverlight to find out the Child's true DesiredSize - if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && (Size.Empty == _childActualSize)) + if (IsSizeSmaller(finalSizeTransformed, arrangedsize) && _childActualSize.IsDefault) { //// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used //// Make a note of the actual DesiredSize @@ -102,7 +102,7 @@ namespace Avalonia.Controls else { // Clear the "need to measure/arrange again" flag - _childActualSize = Size.Empty; + _childActualSize = default; } // Return result to perform the transformation @@ -122,7 +122,7 @@ namespace Avalonia.Controls } Size measureSize; - if (_childActualSize == Size.Empty) + if (_childActualSize.IsDefault) { // Determine the largest size after the transformation measureSize = ComputeLargestTransformedSize(availableSize); @@ -206,7 +206,7 @@ namespace Avalonia.Controls /// /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). /// - private Size _childActualSize = Size.Empty; + private Size _childActualSize = default; /// /// RenderTransform/MatrixTransform applied to TransformRoot. @@ -281,7 +281,7 @@ namespace Avalonia.Controls private Size ComputeLargestTransformedSize(Size arrangeBounds) { // Computed largest transformed size - Size computedSize = Size.Empty; + Size computedSize = default; // Detect infinite bounds and constrain the scenario bool infiniteWidth = double.IsInfinity(arrangeBounds.Width); diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 924c4567f6..96c9b7b5d9 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -77,7 +77,7 @@ namespace Avalonia.Controls.Presenters } /// - Size IScrollable.Extent => Virtualizer?.Extent ?? Size.Empty; + Size IScrollable.Extent => Virtualizer?.Extent ?? default; /// Vector IScrollable.Offset @@ -136,12 +136,12 @@ namespace Avalonia.Controls.Presenters /// protected override Size MeasureOverride(Size availableSize) { - return Virtualizer?.MeasureOverride(availableSize) ?? Size.Empty; + return Virtualizer?.MeasureOverride(availableSize) ?? default; } protected override Size ArrangeOverride(Size finalSize) { - return Virtualizer?.ArrangeOverride(finalSize) ?? Size.Empty; + return Virtualizer?.ArrangeOverride(finalSize) ?? default; } /// diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index 1a7218ff2a..e2a13512a5 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -292,7 +292,7 @@ namespace Avalonia.Controls.Shapes return finalSize; } - return Size.Empty; + return default; } internal static (Size size, Matrix transform) CalculateSizeAndTransform(Size availableSize, Rect shapeBounds, Stretch Stretch) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index a2aba84c34..2d1dad6be0 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -569,7 +569,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var target = CreateTarget(itemCount: 10); var items = (IList)target.Items; target.ApplyTemplate(); - target.Measure(Size.Empty); + target.Measure(default(Size)); target.Arrange(default); // Check for issue #591: this should not throw. From 6bd6b26a99d86b35b5a0e15594142688e8cb11c3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 5 Dec 2022 18:33:32 +0100 Subject: [PATCH 013/213] fix: Missing RegEx RegexOptions.Compiled --- src/Avalonia.Base/Media/UnicodeRange.cs | 2 +- .../Internal/ManagedFileChooserFilterViewModel.cs | 2 +- src/Avalonia.Remote.Protocol/MetsysBson.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Media/UnicodeRange.cs b/src/Avalonia.Base/Media/UnicodeRange.cs index 344b85bae9..bdad870878 100644 --- a/src/Avalonia.Base/Media/UnicodeRange.cs +++ b/src/Avalonia.Base/Media/UnicodeRange.cs @@ -104,7 +104,7 @@ namespace Avalonia.Media public readonly struct UnicodeRangeSegment { - private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$"); + private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$", RegexOptions.Compiled); public UnicodeRangeSegment(int start, int end) { diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs index 8389a25386..6da7c68a75 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs @@ -19,7 +19,7 @@ namespace Avalonia.Dialogs.Internal } _patterns = filter.Patterns? - .Select(e => new Regex(Regex.Escape(e).Replace(@"\*", ".*").Replace(@"\?", "."), RegexOptions.Singleline | RegexOptions.IgnoreCase)) + .Select(e => new Regex(Regex.Escape(e).Replace(@"\*", ".*").Replace(@"\?", "."), RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled)) .ToArray(); } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index d011a963be..2ebeda8403 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1379,7 +1379,7 @@ namespace Metsys.Bson if (optionsString.Contains('w')) options = options | RegexOptions.IgnorePatternWhitespace; if (optionsString.Contains('x')) options = options | RegexOptions.ExplicitCapture; - return new Regex(pattern, options); + return new Regex(pattern, options | RegexOptions.Compiled); } private Types ReadType() From 4e6f379334430a6fa5139a15c3d1d03484a6a1e7 Mon Sep 17 00:00:00 2001 From: SKProCH Date: Mon, 5 Dec 2022 20:50:13 +0300 Subject: [PATCH 014/213] Make all TextElement properties affect TextPresenter's render --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index cc1fa6c513..1381160550 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -110,7 +110,8 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender(CaretBrushProperty, SelectionBrushProperty); + AffectsRender(CaretBrushProperty, SelectionBrushProperty, + TextElement.FontFamilyProperty, TextElement.FontSizeProperty, TextElement.FontStyleProperty, TextElement.FontWeightProperty, TextElement.FontStretchProperty, TextElement.ForegroundProperty); } public TextPresenter() From 3b8a2a9be2153a4c9660f17f1c53c450ebe15b89 Mon Sep 17 00:00:00 2001 From: SKProCH Date: Tue, 6 Dec 2022 12:14:49 +0300 Subject: [PATCH 015/213] Reformat AffectRender's parameters --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 1381160550..683b8f1768 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -110,8 +110,15 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender(CaretBrushProperty, SelectionBrushProperty, - TextElement.FontFamilyProperty, TextElement.FontSizeProperty, TextElement.FontStyleProperty, TextElement.FontWeightProperty, TextElement.FontStretchProperty, TextElement.ForegroundProperty); + AffectsRender( + CaretBrushProperty, + SelectionBrushProperty, + TextElement.FontFamilyProperty, + TextElement.FontSizeProperty, + TextElement.FontStyleProperty, + TextElement.FontWeightProperty, + TextElement.FontStretchProperty, + TextElement.ForegroundProperty); } public TextPresenter() From 0320fc703350195ecadb0229b95bc438b1833115 Mon Sep 17 00:00:00 2001 From: SKProCH Date: Tue, 6 Dec 2022 21:34:25 +0300 Subject: [PATCH 016/213] Remove unnecessary TextElement properties from TextPresenter's AffectRender --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 683b8f1768..7eaff49910 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -110,15 +110,7 @@ namespace Avalonia.Controls.Presenters static TextPresenter() { - AffectsRender( - CaretBrushProperty, - SelectionBrushProperty, - TextElement.FontFamilyProperty, - TextElement.FontSizeProperty, - TextElement.FontStyleProperty, - TextElement.FontWeightProperty, - TextElement.FontStretchProperty, - TextElement.ForegroundProperty); + AffectsRender(CaretBrushProperty, SelectionBrushProperty, TextElement.ForegroundProperty); } public TextPresenter() From 79e5db3f4044f448f9932ddea854f8148b5020e6 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 7 Dec 2022 09:22:16 +0100 Subject: [PATCH 017/213] fix: Address review --- .../Internal/ManagedFileChooserFilterViewModel.cs | 2 +- src/Avalonia.Remote.Protocol/MetsysBson.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs index 6da7c68a75..8389a25386 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserFilterViewModel.cs @@ -19,7 +19,7 @@ namespace Avalonia.Dialogs.Internal } _patterns = filter.Patterns? - .Select(e => new Regex(Regex.Escape(e).Replace(@"\*", ".*").Replace(@"\?", "."), RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Compiled)) + .Select(e => new Regex(Regex.Escape(e).Replace(@"\*", ".*").Replace(@"\?", "."), RegexOptions.Singleline | RegexOptions.IgnoreCase)) .ToArray(); } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 2ebeda8403..c0263b3518 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -1370,7 +1370,7 @@ namespace Metsys.Bson var pattern = ReadName(); var optionsString = ReadName(); - var options = RegexOptions.None; + var options = RegexOptions.Compiled; if (optionsString.Contains('e')) options = options | RegexOptions.ECMAScript; if (optionsString.Contains('i')) options = options | RegexOptions.IgnoreCase; if (optionsString.Contains('l')) options = options | RegexOptions.CultureInvariant; @@ -1379,7 +1379,7 @@ namespace Metsys.Bson if (optionsString.Contains('w')) options = options | RegexOptions.IgnorePatternWhitespace; if (optionsString.Contains('x')) options = options | RegexOptions.ExplicitCapture; - return new Regex(pattern, options | RegexOptions.Compiled); + return new Regex(pattern, options); } private Types ReadType() From e6b8a7369d482bd94d1fac538aaea6d8961f1297 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Dec 2022 08:13:36 +0000 Subject: [PATCH 018/213] simply controlcatalog demo - to revert. --- samples/ControlCatalog/MainView.xaml | 232 +----------------------- samples/ControlCatalog/MainView.xaml.cs | 63 ------- 2 files changed, 6 insertions(+), 289 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 4a5f5bc96c..bdf5d15b6e 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -6,230 +6,10 @@ xmlns:pages="using:ControlCatalog.Pages" xmlns:viewModels="using:ControlCatalog.ViewModels" x:DataType="viewModels:MainWindowViewModel"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - None - BorderOnly - Full - - - - - FluentLight - FluentDark - SimpleLight - SimpleDark - - - - - None - Transparent - Blur - AcrylicBlur - Mica - - - - - LeftToRight - RightToLeft - - - - - - - - + + + + + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 15e666ae7b..ab36a74c12 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -17,69 +17,6 @@ namespace ControlCatalog { AvaloniaXamlLoader.Load(this); - var sideBar = this.Get("Sidebar"); - - if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime) - { - var tabItems = (sideBar.Items as IList); - tabItems?.Add(new TabItem() - { - Header = "Screens", - Content = new ScreenPage() - }); - } - - var themes = this.Get("Themes"); - themes.SelectedItem = App.CurrentTheme; - themes.SelectionChanged += (sender, e) => - { - if (themes.SelectedItem is CatalogTheme theme) - { - App.SetThemeVariant(theme); - } - }; - - var flowDirections = this.Get("FlowDirection"); - flowDirections.SelectionChanged += (sender, e) => - { - if (flowDirections.SelectedItem is FlowDirection flowDirection) - { - this.FlowDirection = flowDirection; - } - }; - - var decorations = this.Get("Decorations"); - decorations.SelectionChanged += (sender, e) => - { - if (VisualRoot is Window window - && decorations.SelectedItem is SystemDecorations systemDecorations) - { - window.SystemDecorations = systemDecorations; - } - }; - - var transparencyLevels = this.Get("TransparencyLevels"); - IDisposable? backgroundSetter = null, paneBackgroundSetter = null; - transparencyLevels.SelectionChanged += (sender, e) => - { - backgroundSetter?.Dispose(); - paneBackgroundSetter?.Dispose(); - if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected - && selected != WindowTransparencyLevel.None) - { - var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.5); - backgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); - paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); - } - }; - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - var decorations = this.Get("Decorations"); - if (VisualRoot is Window window) - decorations.SelectedIndex = (int)window.SystemDecorations; } } } From 34a19ae48d3256d0b8d0692d5826b56febb87a07 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Dec 2022 08:14:05 +0000 Subject: [PATCH 019/213] make web canvas transparent by clearing gl buffers --- .../Avalonia.Browser/webapp/modules/avalonia/canvas.ts | 4 ++++ src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts index 47c501cbb7..c11493a68d 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts @@ -114,6 +114,10 @@ export class Canvas { this.htmlCanvas.height = this.newHeight ?? 0; } + const GL = (globalThis as any).AvaloniaGL; + const GLctx = GL.currentContext.GLctx as WebGLRenderingContext; + GLctx.clearColor(0.0, 0.0, 0.0, 0.0); + GLctx.clear(GLctx.COLOR_BUFFER_BIT | GLctx.DEPTH_BUFFER_BIT); this.renderFrameCallback(); this.renderLoopRequest = 0; diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts index 385cdd4c41..e8e37faf58 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/dom.ts @@ -17,7 +17,6 @@ export class AvaloniaDOM { const canvas = document.createElement("canvas"); canvas.id = `canvas${randomIdPart}`; canvas.classList.add("avalonia-canvas"); - canvas.style.backgroundColor = "#ccc"; canvas.style.width = "100%"; canvas.style.position = "absolute"; From 42fa1c23771d7dc48d531a498a19bb801adfde26 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 8 Dec 2022 08:14:14 +0000 Subject: [PATCH 020/213] add missing launch settings. --- .../Properties/launchSettings.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 samples/ControlCatalog.Browser/Properties/launchSettings.json diff --git a/samples/ControlCatalog.Browser/Properties/launchSettings.json b/samples/ControlCatalog.Browser/Properties/launchSettings.json new file mode 100644 index 0000000000..76c1834e3c --- /dev/null +++ b/samples/ControlCatalog.Browser/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "ControlCatalog.Browser": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/debug?browser={browserInspectUri}" + } + } +} From 7d0367119cb9330635127616238f9bfe1c9ca424 Mon Sep 17 00:00:00 2001 From: startewho Date: Fri, 9 Dec 2022 13:21:49 +0800 Subject: [PATCH 021/213] fix the separator can not show --- src/Avalonia.Controls/NativeMenuItemSeparator.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs index 97278130b1..f55d714884 100644 --- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs +++ b/src/Avalonia.Controls/NativeMenuItemSeparator.cs @@ -1,7 +1,10 @@ namespace Avalonia.Controls { - public class NativeMenuItemSeparator : NativeMenuItemBase + public class NativeMenuItemSeparator : NativeMenuItem { - + public NativeMenuItemSeparator() + { + Header = "-"; + } } } From e3b2724f5a90cd7d0010d8ff14244e842a3b0dbf Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 9 Dec 2022 03:31:14 -0500 Subject: [PATCH 022/213] Fix annoying warnings --- .../Avalonia.Browser.Blazor.csproj | 1 + .../Avalonia.Browser.Blazor/AvaloniaView.cs | 3 ++- .../BlazorSingleViewLifetime.cs | 1 - src/Browser/Avalonia.Browser/AvaloniaView.cs | 1 - .../Avalonia.Browser/BrowserSingleViewLifetime.cs | 2 -- .../Avalonia.Browser/BrowserTopLevelImpl.cs | 4 +++- .../Avalonia.Browser/Interop/CanvasHelper.cs | 1 - .../Skia/BrowserSkiaGpuRenderTarget.cs | 14 ++++++-------- .../Avalonia.Browser/Storage/BlobReadableStream.cs | 1 - .../Storage/BrowserStorageProvider.cs | 1 - .../Avalonia.Browser/Storage/WriteableStream.cs | 1 - 11 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj index a2f1b55b6f..a9cad0538f 100644 --- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj +++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs b/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs index 2cc74273c0..68efea31d6 100644 --- a/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser.Blazor/AvaloniaView.cs @@ -8,9 +8,10 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using BrowserView = Avalonia.Browser.AvaloniaView; +[assembly: SupportedOSPlatform("browser")] + namespace Avalonia.Browser.Blazor; -[SupportedOSPlatform("browser")] public class AvaloniaView : ComponentBase { private Browser.AvaloniaView? _browserView; diff --git a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs index 2432dc29a3..6e7f0de382 100644 --- a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs @@ -5,7 +5,6 @@ using Avalonia.Controls.ApplicationLifetimes; namespace Avalonia.Browser.Blazor; -[SupportedOSPlatform("browser")] public static class WebAppBuilder { public static T SetupWithSingleViewLifetime( diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs index a407e1e4d8..8bc09318d7 100644 --- a/src/Browser/Avalonia.Browser/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs @@ -17,7 +17,6 @@ using SkiaSharp; namespace Avalonia.Browser { - [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings public partial class AvaloniaView : ITextInputMethodImpl { private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never); diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index ee4f6eca9b..384c8d58dc 100644 --- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs @@ -7,7 +7,6 @@ using Avalonia.Platform; namespace Avalonia.Browser; -[SupportedOSPlatform("browser")] public class BrowserSingleViewLifetime : ISingleViewApplicationLifetime { public AvaloniaView? View; @@ -24,7 +23,6 @@ public class BrowserPlatformOptions public Func FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}"); } -[SupportedOSPlatform("browser")] public static class WebAppBuilder { public static T SetupBrowserApp( diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 69e2d27181..6bf22767c0 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Runtime.Versioning; using Avalonia.Browser.Skia; using Avalonia.Browser.Storage; using Avalonia.Controls; @@ -14,9 +15,10 @@ using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; +[assembly: SupportedOSPlatform("browser")] + namespace Avalonia.Browser { - [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider { private Size _clientSize; diff --git a/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs index 8321b00658..27a2b1dcb7 100644 --- a/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/CanvasHelper.cs @@ -6,7 +6,6 @@ namespace Avalonia.Browser.Interop; internal record GLInfo(int ContextId, uint FboId, int Stencils, int Samples, int Depth); -[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings internal static partial class CanvasHelper { diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs index f69dd3c344..9424122ce8 100644 --- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpuRenderTarget.cs @@ -14,14 +14,12 @@ namespace Avalonia.Browser.Skia _size = browserSkiaSurface.Size; var glFbInfo = new GRGlFramebufferInfo(browserSkiaSurface.GlInfo.FboId, browserSkiaSurface.ColorType.ToGlSizedFormat()); - { - _browserSkiaSurface = browserSkiaSurface; - _renderTarget = new GRBackendRenderTarget( - (int)(browserSkiaSurface.Size.Width * browserSkiaSurface.Scaling), - (int)(browserSkiaSurface.Size.Height * browserSkiaSurface.Scaling), - browserSkiaSurface.GlInfo.Samples, - browserSkiaSurface.GlInfo.Stencils, glFbInfo); - } + _browserSkiaSurface = browserSkiaSurface; + _renderTarget = new GRBackendRenderTarget( + (int)(browserSkiaSurface.Size.Width * browserSkiaSurface.Scaling), + (int)(browserSkiaSurface.Size.Height * browserSkiaSurface.Scaling), + browserSkiaSurface.GlInfo.Samples, + browserSkiaSurface.GlInfo.Stencils, glFbInfo); } public void Dispose() diff --git a/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs b/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs index 4fce190346..3404452bca 100644 --- a/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs +++ b/src/Browser/Avalonia.Browser/Storage/BlobReadableStream.cs @@ -7,7 +7,6 @@ using Avalonia.Browser.Interop; namespace Avalonia.Browser.Storage; -[System.Runtime.Versioning.SupportedOSPlatform("browser")] internal class BlobReadableStream : Stream { private JSObject? _jSReference; diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index 28de55092b..dc580f0fcb 100644 --- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -13,7 +13,6 @@ namespace Avalonia.Browser.Storage; internal record FilePickerAcceptType(string Description, IReadOnlyDictionary> Accept); -[SupportedOSPlatform("browser")] internal class BrowserStorageProvider : IStorageProvider { internal const string PickerCancelMessage = "The user aborted a request"; diff --git a/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs b/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs index f29f7420ac..b2f14b6a7a 100644 --- a/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs +++ b/src/Browser/Avalonia.Browser/Storage/WriteableStream.cs @@ -7,7 +7,6 @@ using Avalonia.Browser.Interop; namespace Avalonia.Browser.Storage; -[System.Runtime.Versioning.SupportedOSPlatform("browser")] // Loose wrapper implementaion of a stream on top of FileAPI FileSystemWritableFileStream internal sealed class WriteableStream : Stream { From c757edeea5246737e2d8c2a0b573d1a52aaf00c2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 9 Dec 2022 03:31:36 -0500 Subject: [PATCH 023/213] Fix WASM SetTransparencyLevelHint --- src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 6bf22767c0..95efbe2304 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -202,7 +202,11 @@ namespace Avalonia.Browser public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { - + if (transparencyLevel == WindowTransparencyLevel.None + || transparencyLevel == WindowTransparencyLevel.Transparent) + { + TransparencyLevel = transparencyLevel; + } } public Size ClientSize => _clientSize; @@ -222,7 +226,7 @@ namespace Avalonia.Browser public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IKeyboardDevice KeyboardDevice { get; } = BrowserWindowingPlatform.Keyboard; - public WindowTransparencyLevel TransparencyLevel { get; } + public WindowTransparencyLevel TransparencyLevel { get; private set; } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } public ITextInputMethodImpl TextInputMethod => _avaloniaView; From 0dc65db7baac55d4e57c75087142a113ca59a71c Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 9 Dec 2022 05:11:26 -0500 Subject: [PATCH 024/213] Revert "simply controlcatalog demo - to revert." This reverts commit e6b8a7369d482bd94d1fac538aaea6d8961f1297. --- samples/ControlCatalog/MainView.xaml | 232 +++++++++++++++++++++++- samples/ControlCatalog/MainView.xaml.cs | 63 +++++++ 2 files changed, 289 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index bdf5d15b6e..4a5f5bc96c 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -6,10 +6,230 @@ xmlns:pages="using:ControlCatalog.Pages" xmlns:viewModels="using:ControlCatalog.ViewModels" x:DataType="viewModels:MainWindowViewModel"> - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + None + BorderOnly + Full + + + + + FluentLight + FluentDark + SimpleLight + SimpleDark + + + + + None + Transparent + Blur + AcrylicBlur + Mica + + + + + LeftToRight + RightToLeft + + + + + + + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index ab36a74c12..15e666ae7b 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -17,6 +17,69 @@ namespace ControlCatalog { AvaloniaXamlLoader.Load(this); + var sideBar = this.Get("Sidebar"); + + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime) + { + var tabItems = (sideBar.Items as IList); + tabItems?.Add(new TabItem() + { + Header = "Screens", + Content = new ScreenPage() + }); + } + + var themes = this.Get("Themes"); + themes.SelectedItem = App.CurrentTheme; + themes.SelectionChanged += (sender, e) => + { + if (themes.SelectedItem is CatalogTheme theme) + { + App.SetThemeVariant(theme); + } + }; + + var flowDirections = this.Get("FlowDirection"); + flowDirections.SelectionChanged += (sender, e) => + { + if (flowDirections.SelectedItem is FlowDirection flowDirection) + { + this.FlowDirection = flowDirection; + } + }; + + var decorations = this.Get("Decorations"); + decorations.SelectionChanged += (sender, e) => + { + if (VisualRoot is Window window + && decorations.SelectedItem is SystemDecorations systemDecorations) + { + window.SystemDecorations = systemDecorations; + } + }; + + var transparencyLevels = this.Get("TransparencyLevels"); + IDisposable? backgroundSetter = null, paneBackgroundSetter = null; + transparencyLevels.SelectionChanged += (sender, e) => + { + backgroundSetter?.Dispose(); + paneBackgroundSetter?.Dispose(); + if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected + && selected != WindowTransparencyLevel.None) + { + var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.5); + backgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + } + }; + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + var decorations = this.Get("Decorations"); + if (VisualRoot is Window window) + decorations.SelectedIndex = (int)window.SystemDecorations; } } } From 001484e71913ef880149f5986dd47d5b5ba18d9a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 9 Dec 2022 05:32:04 -0500 Subject: [PATCH 025/213] Remove glctx hack --- .../Avalonia.Browser/webapp/modules/avalonia/canvas.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts index c11493a68d..47c501cbb7 100644 --- a/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts +++ b/src/Browser/Avalonia.Browser/webapp/modules/avalonia/canvas.ts @@ -114,10 +114,6 @@ export class Canvas { this.htmlCanvas.height = this.newHeight ?? 0; } - const GL = (globalThis as any).AvaloniaGL; - const GLctx = GL.currentContext.GLctx as WebGLRenderingContext; - GLctx.clearColor(0.0, 0.0, 0.0, 0.0); - GLctx.clear(GLctx.COLOR_BUFFER_BIT | GLctx.DEPTH_BUFFER_BIT); this.renderFrameCallback(); this.renderLoopRequest = 0; From 08a494ac7df58ee7f8f346d42572bc861ff91a6b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 9 Dec 2022 05:32:21 -0500 Subject: [PATCH 026/213] Update view transparency selection on the control catalog --- samples/ControlCatalog/MainView.xaml | 3 +-- samples/ControlCatalog/MainView.xaml.cs | 23 +++++++++++++------ samples/ControlCatalog/MainWindow.xaml | 1 - .../Pages/WindowCustomizationsPage.xaml | 7 ------ .../ViewModels/MainWindowViewModel.cs | 7 ------ 5 files changed, 17 insertions(+), 24 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 4a5f5bc96c..1e558f0263 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -206,8 +206,7 @@ + HorizontalAlignment="Stretch"> None Transparent diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 15e666ae7b..f3c1a68e72 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.VisualTree; using ControlCatalog.Models; using ControlCatalog.Pages; @@ -59,17 +60,25 @@ namespace ControlCatalog }; var transparencyLevels = this.Get("TransparencyLevels"); - IDisposable? backgroundSetter = null, paneBackgroundSetter = null; + IDisposable? topLevelBackgroundSideSetter = null, sideBarBackgroundSetter = null, paneBackgroundSetter = null; transparencyLevels.SelectionChanged += (sender, e) => { - backgroundSetter?.Dispose(); + topLevelBackgroundSideSetter?.Dispose(); + sideBarBackgroundSetter?.Dispose(); paneBackgroundSetter?.Dispose(); - if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected - && selected != WindowTransparencyLevel.None) + if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected) { - var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.5); - backgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); - paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + var topLevel = (TopLevel)this.GetVisualRoot()!; + topLevel.TransparencyLevelHint = selected; + + if (selected != WindowTransparencyLevel.None) + { + var transparentBrush = new ImmutableSolidColorBrush(Colors.White, 0); + var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.2); + topLevelBackgroundSideSetter = topLevel.SetValue(BackgroundProperty, transparentBrush, Avalonia.Data.BindingPriority.Style); + sideBarBackgroundSetter = sideBar.SetValue(BackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + paneBackgroundSetter = sideBar.SetValue(SplitView.PaneBackgroundProperty, semiTransparentBrush, Avalonia.Data.BindingPriority.Style); + } } }; } diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index cebb3e0916..442c1d37b0 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -10,7 +10,6 @@ ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}" ExtendClientAreaChromeHints="{Binding ChromeHints}" ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}" - TransparencyLevelHint="{Binding TransparencyLevel}" x:Name="MainWindow" Background="Transparent" x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml index f1403905c4..d690058b27 100644 --- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml +++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml @@ -11,12 +11,5 @@ - - None - Transparent - Blur - AcrylicBlur - Mica - diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index b79eff780c..3bb4202166 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -17,7 +17,6 @@ namespace ControlCatalog.ViewModels private bool _isMenuItemChecked = true; private WindowState _windowState; private WindowState[] _windowStates = Array.Empty(); - private int _transparencyLevel; private ExtendClientAreaChromeHints _chromeHints = ExtendClientAreaChromeHints.PreferSystemChrome; private bool _extendClientAreaEnabled; private bool _systemTitleBarEnabled; @@ -77,12 +76,6 @@ namespace ControlCatalog.ViewModels TitleBarHeight = -1; } - public int TransparencyLevel - { - get { return _transparencyLevel; } - set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); } - } - public ExtendClientAreaChromeHints ChromeHints { get { return _chromeHints; } From e29257c52bdd22371de5588c302630f64d23d566 Mon Sep 17 00:00:00 2001 From: Michal Tomecki Date: Sun, 11 Dec 2022 23:15:57 +0100 Subject: [PATCH 027/213] Fixed incorrect startup window position on Linux --- src/Avalonia.X11/X11Window.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 160401c5fc..955096e18a 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -298,7 +298,7 @@ namespace Avalonia.X11 min_height = min.Height }; hints.height_inc = hints.width_inc = 1; - var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc | XSizeHintsFlags.PPosition | XSizeHintsFlags.PSize; + var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc; // People might be passing double.MaxValue if (max.Width < 100000 && max.Height < 100000) { From 07e9bad0aed1b26f70dd952051261038f11f3a4c Mon Sep 17 00:00:00 2001 From: Michal Tomecki Date: Mon, 12 Dec 2022 12:11:35 +0100 Subject: [PATCH 028/213] Updated Window startup positioning behaviour on Linux to be more consistent with WPF behaviour --- src/Avalonia.X11/X11Window.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 955096e18a..62e4441066 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -57,6 +57,7 @@ namespace Avalonia.X11 private TransparencyHelper _transparencyHelper; private RawEventGrouper _rawEventGrouper; private bool _useRenderWindow = false; + private bool _usePositioningFlags = false; enum XSyncState { @@ -299,6 +300,9 @@ namespace Avalonia.X11 }; hints.height_inc = hints.width_inc = 1; var flags = XSizeHintsFlags.PMinSize | XSizeHintsFlags.PResizeInc; + if (_usePositioningFlags) + flags |= XSizeHintsFlags.PPosition | XSizeHintsFlags.PSize; + // People might be passing double.MaxValue if (max.Width < 100000 && max.Height < 100000) { @@ -958,6 +962,9 @@ namespace Avalonia.X11 get => _position ?? default; set { + _usePositioningFlags = true; + UpdateSizeHints(null); + var changes = new XWindowChanges { x = (int)value.X, From 4bb3a0d8bebce6eeadca964f36f729f55bc46621 Mon Sep 17 00:00:00 2001 From: Julian Date: Mon, 12 Dec 2022 11:55:28 +0000 Subject: [PATCH 029/213] Add missing SystemControlBackgroundAltLowBrush in fluent theme --- src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml | 1 + src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml index 89841c92c0..0192fb1b54 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseDark.xaml @@ -36,6 +36,7 @@ + diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml index 89b646fb52..a9e5ed949a 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseLight.xaml @@ -36,6 +36,7 @@ + From cc7427dd721f436b481a4f71c296b13fd6f22af0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 12 Dec 2022 14:06:22 +0100 Subject: [PATCH 030/213] Don't send KeyUp/KeyDown events for Key.None. --- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 5537a0b704..fe7d881f11 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -134,13 +134,18 @@ namespace Avalonia.Win32 case WindowsMessage.WM_KEYDOWN: case WindowsMessage.WM_SYSKEYDOWN: { - e = new RawKeyEventArgs( - WindowsKeyboardDevice.Instance, - timestamp, - _owner, - RawKeyEventType.KeyDown, - KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), - WindowsKeyboardDevice.Instance.Modifiers); + var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); + + if (key != Key.None) + { + e = new RawKeyEventArgs( + WindowsKeyboardDevice.Instance, + timestamp, + _owner, + RawKeyEventType.KeyDown, + key, + WindowsKeyboardDevice.Instance.Modifiers); + } break; } @@ -159,13 +164,18 @@ namespace Avalonia.Win32 case WindowsMessage.WM_KEYUP: case WindowsMessage.WM_SYSKEYUP: { - e = new RawKeyEventArgs( + var key = KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)); + + if (key != Key.None) + { + e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, timestamp, _owner, RawKeyEventType.KeyUp, - KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), + key, WindowsKeyboardDevice.Instance.Modifiers); + } break; } case WindowsMessage.WM_CHAR: From b0aaf543e843c43ebed3803c1817743b5597e03d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 12 Dec 2022 13:39:34 +0000 Subject: [PATCH 031/213] add android transparency support --- .../ControlCatalog.Android/MainActivity.cs | 2 +- .../Resources/values/styles.xml | 4 +++ .../ControlCatalog.Android/SplashActivity.cs | 2 ++ src/Android/Avalonia.Android/AvaloniaView.cs | 2 ++ .../InvalidationAwareSurfaceView.cs | 1 + .../Platform/SkiaPlatform/TopLevelImpl.cs | 31 +++++++++++++++++-- 6 files changed, 39 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 62c582610c..f6fa07dbde 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,7 +5,7 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.Main", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] public class MainActivity : AvaloniaMainActivity { } diff --git a/samples/ControlCatalog.Android/Resources/values/styles.xml b/samples/ControlCatalog.Android/Resources/values/styles.xml index 2759d2904a..49e079a719 100644 --- a/samples/ControlCatalog.Android/Resources/values/styles.xml +++ b/samples/ControlCatalog.Android/Resources/values/styles.xml @@ -14,4 +14,8 @@ @null + + diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs index 908b5f082a..a0b68b129b 100644 --- a/samples/ControlCatalog.Android/SplashActivity.cs +++ b/samples/ControlCatalog.Android/SplashActivity.cs @@ -28,6 +28,8 @@ namespace ControlCatalog.Android base.OnResume(); StartActivity(new Intent(Application.Context, typeof(MainActivity))); + + Finish(); } } } diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 5267843bfc..f7e32f99db 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -24,6 +24,8 @@ namespace Avalonia.Android _root = new EmbeddableControlRoot(_view); _root.Prepare(); + + this.SetBackgroundColor(global::Android.Graphics.Color.Transparent); } internal TopLevelImpl TopLevelImpl => _view; diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs index 33501ece06..f205458f0e 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs @@ -22,6 +22,7 @@ namespace Avalonia.Android public InvalidationAwareSurfaceView(Context context) : base(context) { Holder.AddCallback(this); + Holder.SetFormat(global::Android.Graphics.Format.Transparent); _handler = new Handler(context.MainLooper); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 4150b52946..62f047b826 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -27,6 +27,8 @@ using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; using AndroidRect = Android.Graphics.Rect; +using AndroidX.Core.App; +using Android.Graphics.Drawables; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -279,7 +281,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action LostFocus { get; set; } public Action TransparencyLevelChanged { get; set; } - public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; + public WindowTransparencyLevel TransparencyLevel { get; private set; } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1); @@ -297,7 +299,32 @@ namespace Avalonia.Android.Platform.SkiaPlatform public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { - throw new NotImplementedException(); + if (TransparencyLevel != transparencyLevel) + { + bool isLegacy = Build.VERSION.SdkInt < BuildVersionCodes.R; + if (_view.Context is AvaloniaMainActivity activity) + { + switch (transparencyLevel) + { + case WindowTransparencyLevel.None: + if (!isLegacy) + { + activity.SetTranslucent(false); + } + break; + case WindowTransparencyLevel.Transparent: + if (!isLegacy) + { + activity.SetTranslucent(true); + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + break; + default: + return; + } + TransparencyLevel = transparencyLevel; + } + } } } From 8c16d0420db4b064ff990a33fa6e88253e81caf2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:10:40 +0100 Subject: [PATCH 032/213] fix: Drop Direct3DInteropSample --- Avalonia.sln | 11 +- .../interop/Direct3DInteropSample/App.paml | 5 - .../interop/Direct3DInteropSample/App.paml.cs | 21 -- .../Direct3DInteropSample.csproj | 32 -- .../Direct3DInteropSample/MainWindow.cs | 283 ------------------ .../Direct3DInteropSample/MainWindow.paml | 14 - .../MainWindowViewModel.cs | 45 --- .../interop/Direct3DInteropSample/MiniCube.fx | 47 --- .../interop/Direct3DInteropSample/Program.cs | 16 - 9 files changed, 2 insertions(+), 472 deletions(-) delete mode 100644 samples/interop/Direct3DInteropSample/App.paml delete mode 100644 samples/interop/Direct3DInteropSample/App.paml.cs delete mode 100644 samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj delete mode 100644 samples/interop/Direct3DInteropSample/MainWindow.cs delete mode 100644 samples/interop/Direct3DInteropSample/MainWindow.paml delete mode 100644 samples/interop/Direct3DInteropSample/MainWindowViewModel.cs delete mode 100644 samples/interop/Direct3DInteropSample/MiniCube.fx delete mode 100644 samples/interop/Direct3DInteropSample/Program.cs diff --git a/Avalonia.sln b/Avalonia.sln index 7efb294b64..e6898131b0 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -136,8 +136,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linux", "Linux", "{86C53C40 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.LinuxFramebuffer", "src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj", "{854568D5-13D1-4B4F-B50D-534DC7EFD3C9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Direct3DInteropSample", "samples\interop\Direct3DInteropSample\Direct3DInteropSample.csproj", "{638580B0-7910-40EF-B674-DCB34DA308CD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.Interop", "src\Windows\Avalonia.Win32.Interop\Avalonia.Win32.Interop.csproj", "{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.Skia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}" @@ -228,9 +226,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser", "src\Bro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}" EndProject @@ -366,10 +364,6 @@ Global {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {854568D5-13D1-4B4F-B50D-534DC7EFD3C9}.Release|Any CPU.Build.0 = Release|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {638580B0-7910-40EF-B674-DCB34DA308CD}.Release|Any CPU.Build.0 = Release|Any CPU {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Debug|Any CPU.Build.0 = Debug|Any CPU {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -580,7 +574,6 @@ Global {7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098} {854568D5-13D1-4B4F-B50D-534DC7EFD3C9} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} - {638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888} {E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098} diff --git a/samples/interop/Direct3DInteropSample/App.paml b/samples/interop/Direct3DInteropSample/App.paml deleted file mode 100644 index e6d77dfaf4..0000000000 --- a/samples/interop/Direct3DInteropSample/App.paml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/samples/interop/Direct3DInteropSample/App.paml.cs b/samples/interop/Direct3DInteropSample/App.paml.cs deleted file mode 100644 index 29365decfe..0000000000 --- a/samples/interop/Direct3DInteropSample/App.paml.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Avalonia; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Markup.Xaml; - -namespace Direct3DInteropSample -{ - public class App : Application - { - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) - desktop.MainWindow = new MainWindow(); - base.OnFrameworkInitializationCompleted(); - } - } -} diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj deleted file mode 100644 index f9ef4693d5..0000000000 --- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - Exe - net461 - - - - - - %(Filename) - - - Designer - - - - - - - - PreserveNewest - - - - - - - - - - - diff --git a/samples/interop/Direct3DInteropSample/MainWindow.cs b/samples/interop/Direct3DInteropSample/MainWindow.cs deleted file mode 100644 index 6cc3cb9116..0000000000 --- a/samples/interop/Direct3DInteropSample/MainWindow.cs +++ /dev/null @@ -1,283 +0,0 @@ -using System; - -using Avalonia; -using Avalonia.Controls; -using Avalonia.Direct2D1; -using Avalonia.Direct2D1.Media; -using Avalonia.Markup.Xaml; -using Avalonia.Platform; -using Avalonia.Rendering; - -using SharpDX; -using SharpDX.D3DCompiler; -using SharpDX.Direct2D1; -using SharpDX.Direct3D; -using SharpDX.Direct3D11; -using SharpDX.DXGI; - -using AlphaMode = SharpDX.Direct2D1.AlphaMode; -using Buffer = SharpDX.Direct3D11.Buffer; -using DeviceContext = SharpDX.Direct2D1.DeviceContext; -using Factory2 = SharpDX.DXGI.Factory2; -using InputElement = SharpDX.Direct3D11.InputElement; -using Matrix = SharpDX.Matrix; -using PixelFormat = SharpDX.Direct2D1.PixelFormat; -using Resource = SharpDX.Direct3D11.Resource; - -namespace Direct3DInteropSample -{ - public class MainWindow : Window - { - Texture2D _backBuffer; - RenderTargetView _renderView; - Texture2D _depthBuffer; - DepthStencilView _depthView; - private readonly SwapChain _swapChain; - private SwapChainDescription1 _desc; - private Matrix _proj = Matrix.Identity; - private readonly Matrix _view; - private Buffer _contantBuffer; - private DeviceContext _deviceContext; - private readonly MainWindowViewModel _model; - - public MainWindow() - { - DataContext = _model = new MainWindowViewModel(); - - _desc = new SwapChainDescription1() - { - BufferCount = 1, - Width = (int)ClientSize.Width, - Height = (int)ClientSize.Height, - Format = Format.R8G8B8A8_UNorm, - SampleDescription = new SampleDescription(1, 0), - SwapEffect = SwapEffect.Discard, - Usage = Usage.RenderTargetOutput - }; - - using (var factory = Direct2D1Platform.DxgiDevice.Adapter.GetParent()) - { - _swapChain = new SwapChain1(factory, Direct2D1Platform.DxgiDevice, PlatformImpl?.Handle.Handle ?? IntPtr.Zero, ref _desc); - } - - _deviceContext = new DeviceContext(Direct2D1Platform.Direct2D1Device, DeviceContextOptions.None) - { - DotsPerInch = new Size2F(96, 96) - }; - - CreateMesh(); - - _view = Matrix.LookAtLH(new Vector3(0, 0, -5), new Vector3(0, 0, 0), Vector3.UnitY); - - this.GetObservable(ClientSizeProperty).Subscribe(Resize); - - Resize(ClientSize); - - AvaloniaXamlLoader.Load(this); - - Background = Avalonia.Media.Brushes.Transparent; - } - - - protected override void HandlePaint(Rect rect) - { - var viewProj = Matrix.Multiply(_view, _proj); - var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; - - // Clear views - context.ClearDepthStencilView(_depthView, DepthStencilClearFlags.Depth, 1.0f, 0); - context.ClearRenderTargetView(_renderView, Color.White); - - // Update WorldViewProj Matrix - var worldViewProj = Matrix.RotationX((float)_model.RotationX) * Matrix.RotationY((float)_model.RotationY) - * Matrix.RotationZ((float)_model.RotationZ) - * Matrix.Scaling((float)_model.Zoom) - * viewProj; - worldViewProj.Transpose(); - context.UpdateSubresource(ref worldViewProj, _contantBuffer); - - // Draw the cube - context.Draw(36, 0); - base.HandlePaint(rect); - - // Present! - _swapChain.Present(0, PresentFlags.None); - } - - private void CreateMesh() - { - var device = Direct2D1Platform.Direct3D11Device; - - // Compile Vertex and Pixel shaders - var vertexShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "VS", "vs_4_0"); - var vertexShader = new VertexShader(device, vertexShaderByteCode); - - var pixelShaderByteCode = ShaderBytecode.CompileFromFile("MiniCube.fx", "PS", "ps_4_0"); - var pixelShader = new PixelShader(device, pixelShaderByteCode); - - var signature = ShaderSignature.GetInputSignature(vertexShaderByteCode); - - var inputElements = new[] - { - new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0), - new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 16, 0) - }; - - // Layout from VertexShader input signature - var layout = new InputLayout( - device, - signature, - inputElements); - - // Instantiate Vertex buffer from vertex data - var vertices = Buffer.Create( - device, - BindFlags.VertexBuffer, - new[] - { - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), // Front - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), // BACK - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), // Top - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 0.0f, 1.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), // Bottom - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 1.0f, 0.0f, 1.0f), - - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), // Left - new Vector4(-1.0f, -1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, -1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, 1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - new Vector4(-1.0f, 1.0f, -1.0f, 1.0f), new Vector4(1.0f, 0.0f, 1.0f, 1.0f), - - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), // Right - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, -1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, -1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - new Vector4( 1.0f, 1.0f, 1.0f, 1.0f), new Vector4(0.0f, 1.0f, 1.0f, 1.0f), - }); - - // Create Constant Buffer - _contantBuffer = new Buffer(device, Utilities.SizeOf(), ResourceUsage.Default, BindFlags.ConstantBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0); - - var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; - - // Prepare All the stages - context.InputAssembler.InputLayout = layout; - context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; - context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertices, Utilities.SizeOf() * 2, 0)); - context.VertexShader.SetConstantBuffer(0, _contantBuffer); - context.VertexShader.Set(vertexShader); - context.PixelShader.Set(pixelShader); - } - - private void Resize(Size size) - { - Utilities.Dispose(ref _deviceContext); - Utilities.Dispose(ref _backBuffer); - Utilities.Dispose(ref _renderView); - Utilities.Dispose(ref _depthBuffer); - Utilities.Dispose(ref _depthView); - var context = Direct2D1Platform.Direct3D11Device.ImmediateContext; - - // Resize the backbuffer - _swapChain.ResizeBuffers(0, 0, 0, Format.Unknown, SwapChainFlags.None); - - // Get the backbuffer from the swapchain - _backBuffer = Resource.FromSwapChain(_swapChain, 0); - - // Renderview on the backbuffer - _renderView = new RenderTargetView(Direct2D1Platform.Direct3D11Device, _backBuffer); - - // Create the depth buffer - _depthBuffer = new Texture2D( - Direct2D1Platform.Direct3D11Device, - new Texture2DDescription() - { - Format = Format.D32_Float_S8X24_UInt, - ArraySize = 1, - MipLevels = 1, - Width = (int)size.Width, - Height = (int)size.Height, - SampleDescription = new SampleDescription(1, 0), - Usage = ResourceUsage.Default, - BindFlags = BindFlags.DepthStencil, - CpuAccessFlags = CpuAccessFlags.None, - OptionFlags = ResourceOptionFlags.None - }); - - // Create the depth buffer view - _depthView = new DepthStencilView(Direct2D1Platform.Direct3D11Device, _depthBuffer); - - // Setup targets and viewport for rendering - context.Rasterizer.SetViewport(new Viewport(0, 0, (int)size.Width, (int)size.Height, 0.0f, 1.0f)); - context.OutputMerger.SetTargets(_depthView, _renderView); - - // Setup new projection matrix with correct aspect ratio - _proj = Matrix.PerspectiveFovLH((float)Math.PI / 4.0f, (float)(size.Width / size.Height), 0.1f, 100.0f); - - using (var dxgiBackBuffer = _swapChain.GetBackBuffer(0)) - { - var renderTarget = new SharpDX.Direct2D1.RenderTarget( - Direct2D1Platform.Direct2D1Factory, - dxgiBackBuffer, - new RenderTargetProperties - { - DpiX = 96, - DpiY = 96, - Type = RenderTargetType.Default, - PixelFormat = new PixelFormat( - Format.Unknown, - AlphaMode.Premultiplied) - }); - - _deviceContext = renderTarget.QueryInterface(); - - renderTarget.Dispose(); - } - } - - private class D3DRenderTarget : IRenderTarget - { - private readonly MainWindow _window; - - public D3DRenderTarget(MainWindow window) - { - _window = window; - } - - public void Dispose() - { - } - - public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) - { - return new DrawingContextImpl(visualBrushRenderer, null, _window._deviceContext); - } - } - - - protected override IRenderTarget CreateRenderTarget() => new D3DRenderTarget(this); - } -} diff --git a/samples/interop/Direct3DInteropSample/MainWindow.paml b/samples/interop/Direct3DInteropSample/MainWindow.paml deleted file mode 100644 index 37c6265836..0000000000 --- a/samples/interop/Direct3DInteropSample/MainWindow.paml +++ /dev/null @@ -1,14 +0,0 @@ - - - - Rotation X - - Rotation Y - - Rotation Z - - Zoom - - - - \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs b/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs deleted file mode 100644 index 21679a99c5..0000000000 --- a/samples/interop/Direct3DInteropSample/MainWindowViewModel.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using MiniMvvm; - -namespace Direct3DInteropSample -{ - public class MainWindowViewModel : ViewModelBase - { - private double _rotationX; - - public double RotationX - { - get { return _rotationX; } - set { this.RaiseAndSetIfChanged(ref _rotationX, value); } - } - - private double _rotationY = 1; - - public double RotationY - { - get { return _rotationY; } - set { this.RaiseAndSetIfChanged(ref _rotationY, value); } - } - - private double _rotationZ = 2; - - public double RotationZ - { - get { return _rotationZ; } - set { this.RaiseAndSetIfChanged(ref _rotationZ, value); } - } - - - private double _zoom = 1; - - public double Zoom - { - get { return _zoom; } - set { this.RaiseAndSetIfChanged(ref _zoom, value); } - } - } -} diff --git a/samples/interop/Direct3DInteropSample/MiniCube.fx b/samples/interop/Direct3DInteropSample/MiniCube.fx deleted file mode 100644 index f246421f2d..0000000000 --- a/samples/interop/Direct3DInteropSample/MiniCube.fx +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2010-2013 SharpDX - Alexandre Mutel -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -struct VS_IN -{ - float4 pos : POSITION; - float4 col : COLOR; -}; - -struct PS_IN -{ - float4 pos : SV_POSITION; - float4 col : COLOR; -}; - -float4x4 worldViewProj; - -PS_IN VS( VS_IN input ) -{ - PS_IN output = (PS_IN)0; - - output.pos = mul(input.pos, worldViewProj); - output.col = input.col; - - return output; -} - -float4 PS( PS_IN input ) : SV_Target -{ - return input.col; -} \ No newline at end of file diff --git a/samples/interop/Direct3DInteropSample/Program.cs b/samples/interop/Direct3DInteropSample/Program.cs deleted file mode 100644 index bf8e76d7e4..0000000000 --- a/samples/interop/Direct3DInteropSample/Program.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Avalonia; - -namespace Direct3DInteropSample -{ - class Program - { - public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() - .With(new Win32PlatformOptions { UseDeferredRendering = false }) - .UseWin32() - .UseDirect2D1(); - - public static int Main(string[] args) - => BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); - } -} From 4f05fb47b72e563940bb34c86970dcd6fbc79745 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 12 Dec 2022 14:18:05 +0000 Subject: [PATCH 033/213] add blur support for android s + --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 62f047b826..7ec3c5b1f0 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -27,7 +27,6 @@ using Avalonia.Rendering.Composition; using Java.Lang; using Math = System.Math; using AndroidRect = Android.Graphics.Rect; -using AndroidX.Core.App; using Android.Graphics.Drawables; namespace Avalonia.Android.Platform.SkiaPlatform @@ -301,26 +300,81 @@ namespace Avalonia.Android.Platform.SkiaPlatform { if (TransparencyLevel != transparencyLevel) { - bool isLegacy = Build.VERSION.SdkInt < BuildVersionCodes.R; + bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R; + bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; if (_view.Context is AvaloniaMainActivity activity) { switch (transparencyLevel) { + case WindowTransparencyLevel.AcrylicBlur: + case WindowTransparencyLevel.ForceAcrylicBlur: + case WindowTransparencyLevel.Mica: case WindowTransparencyLevel.None: - if (!isLegacy) + if (!isBelowR) { activity.SetTranslucent(false); } + if (isAboveR) + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) + { + attr.BlurBehindRadius = 0; + + activity.Window.Attributes = attr; + } + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + + if(transparencyLevel != WindowTransparencyLevel.None) + { + return; + } break; case WindowTransparencyLevel.Transparent: - if (!isLegacy) + if (!isBelowR) { activity.SetTranslucent(true); } + if (isAboveR) + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) + { + attr.BlurBehindRadius = 0; + + activity.Window.Attributes = attr; + } + } activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); break; - default: - return; + case WindowTransparencyLevel.Blur: + if (isAboveR) + { + activity.SetTranslucent(true); + activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) + { + attr.BlurBehindRadius = 120; + + activity.Window.Attributes = attr; + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } + else + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + + return; + } + break; } TransparencyLevel = transparencyLevel; } From 949c2b94b9755fce76527e40d04f5b72947a6dca Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:17:18 +0100 Subject: [PATCH 034/213] feat: Enable Rule CA1823 --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index 1583d3e469..7995062f9f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -152,6 +152,8 @@ dotnet_diagnostic.CA1820.severity = warning dotnet_diagnostic.CA1821.severity = warning # CA1822: Mark members as static dotnet_diagnostic.CA1822.severity = suggestion +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = warning dotnet_code_quality.CA1822.api_surface = private, internal # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning From ade6f3f1cee1041eadd430329ff4ef39e7f2838f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:47:12 +0100 Subject: [PATCH 035/213] feat(Android): Address Rule CA1823 --- .../Platform/SkiaPlatform/AndroidFramebuffer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs index aabf8160f8..94e5f4bd01 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs @@ -96,12 +96,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IntPtr bits; // Do not touch. +#pragma warning disable CA1823 // Avoid unused private fields uint reserved1; uint reserved2; uint reserved3; uint reserved4; uint reserved5; uint reserved6; +#pragma warning restore CA1823 // Avoid unused private fields } } } From 0d4ddca7fdd0d9b47e38e0b649af7b908186859d Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:47:30 +0100 Subject: [PATCH 036/213] feat(Base): Address Rule CA1823 --- src/Avalonia.Base/Data/Core/ExpressionNode.cs | 2 -- src/Avalonia.Base/Data/Core/ExpressionObserver.cs | 2 -- src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs | 1 - .../Media/TextFormatting/Unicode/CodepointEnumerator.cs | 1 - 4 files changed, 6 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index e4b833176c..5fb2bb5c13 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -4,8 +4,6 @@ namespace Avalonia.Data.Core { public abstract class ExpressionNode { - private static readonly object CacheInvalid = new object(); - protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 0a9f834aeb..2151c100e5 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -49,8 +49,6 @@ namespace Avalonia.Data.Core new TaskStreamPlugin(), new ObservableStreamPlugin(), }; - - private static readonly object UninitializedValue = new object(); private readonly ExpressionNode _node; private object? _root; private Func? _rootGetter; diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index e745a873a2..4472ba87eb 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -8,7 +8,6 @@ namespace Avalonia.Media.TextFormatting internal readonly struct FormattedTextSource : ITextSource { private readonly CharacterBufferRange _text; - private readonly int length; private readonly TextRunProperties _defaultProperties; private readonly IReadOnlyList>? _textModifier; diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs index 330ead476a..a2c36d9a13 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs @@ -5,7 +5,6 @@ namespace Avalonia.Media.TextFormatting.Unicode public ref struct CodepointEnumerator { private CharacterBufferRange _text; - private int _pos; public CodepointEnumerator(CharacterBufferRange text) { From d13290550494c9ea96d369609b1407814ff65747 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:47:58 +0100 Subject: [PATCH 037/213] feat(Controls): Address Rule CA1823 --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 1 - src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 2 -- src/Avalonia.Controls/Window.cs | 6 +----- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index cc1fa6c513..798caf0b15 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -106,7 +106,6 @@ namespace Avalonia.Controls.Presenters private Rect _caretBounds; private Point _navigationPosition; private string? _preeditText; - private CharacterHit _compositionStartHit = new CharacterHit(-1); static TextPresenter() { diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 44fa78ac21..795bc05e23 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -119,8 +119,6 @@ namespace Avalonia.Controls.Primitives /// public static readonly StyledProperty WrapSelectionProperty = AvaloniaProperty.Register(nameof(WrapSelection), defaultValue: false); - - private static readonly IList Empty = Array.Empty(); private string _textSearchTerm = string.Empty; private DispatcherTimer? _textSearchTimer; private ISelectionModel? _selection; diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index a893c74324..b0911403cc 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -169,10 +169,6 @@ namespace Avalonia.Controls /// public static readonly RoutedEvent WindowOpenedEvent = RoutedEvent.Register("WindowOpened", RoutingStrategies.Direct); - - - - private readonly NameScope _nameScope = new NameScope(); private object? _dialogResult; private readonly Size _maxPlatformClientSize; private WindowStartupLocation _windowStartupLocation; @@ -235,7 +231,7 @@ namespace Avalonia.Controls impl.GotInputWhenDisabled = OnGotInputWhenDisabled; impl.WindowStateChanged = HandleWindowStateChanged; _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size); - impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; + impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged; this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x, PlatformResizeReason.Application)); PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar); From 2ce1e636e016b34d7cbc041923c01cbdf68f908b Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:48:16 +0100 Subject: [PATCH 038/213] feat(Diagnostic): Address Rule CA1823 --- src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 42c5ea1fa9..00173dbb35 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -10,8 +10,7 @@ namespace Avalonia.Diagnostics.Controls { private readonly App _application; - private static readonly Version s_version = typeof(AvaloniaObject).Assembly?.GetName()?.Version - ?? Version.Parse("0.0.00"); + public event EventHandler? Closed; public Application(App application) From 779a31f6efc878760f2cdab0587bd4d4a5cd5399 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:48:35 +0100 Subject: [PATCH 039/213] feat(Native): Address Rule CA1823 --- src/Avalonia.Native/IAvnMenu.cs | 1 - src/Avalonia.Native/WindowImplBase.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs index 3e46e0c5c6..e6a5a371d0 100644 --- a/src/Avalonia.Native/IAvnMenu.cs +++ b/src/Avalonia.Native/IAvnMenu.cs @@ -46,7 +46,6 @@ namespace Avalonia.Native.Interop.Impl private AvaloniaNativeMenuExporter _exporter; private List<__MicroComIAvnMenuItemProxy> _menuItems = new List<__MicroComIAvnMenuItemProxy>(); private Dictionary _menuItemLookup = new Dictionary(); - private CompositeDisposable _propertyDisposables = new CompositeDisposable(); public void RaiseNeedsUpdate() { diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 5d66590a2b..ca57e30b1c 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -63,7 +63,6 @@ namespace Avalonia.Native private double _savedScaling; private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; - private IGlContext _glContext; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativeGlPlatformGraphics glFeature) From 868a22d2f326575006f188f6f58662b7828d6419 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:49:03 +0100 Subject: [PATCH 040/213] feat(Linux): Address Rule CA1823 --- src/Avalonia.X11/X11Clipboard.cs | 2 -- src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs | 2 -- .../Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs | 1 - 3 files changed, 5 deletions(-) diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index d428d82744..2aa7797067 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -18,8 +18,6 @@ namespace Avalonia.X11 private TaskCompletionSource _requestedDataTcs; private readonly IntPtr[] _textAtoms; private readonly IntPtr _avaloniaSaveTargetsAtom; - private readonly Dictionary _formatAtoms = new Dictionary(); - private readonly Dictionary _atomFormats = new Dictionary(); public X11Clipboard(AvaloniaX11Platform platform) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs index 1e3c4bed48..686050e7c2 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/EvDev/EvDevBackend.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.EvDev @@ -13,7 +12,6 @@ namespace Avalonia.LinuxFramebuffer.Input.EvDev private readonly EvDevDeviceDescription[] _deviceDescriptions; private readonly List _handlers = new List(); private int _epoll; - private object _lock = new object(); private Action _onInput; private IInputRoot _inputRoot; private RawEventGroupingThreadingHelper _inputQueue; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 77e8202fac..bff9ddc55c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -11,7 +11,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput { private IScreenInfoProvider _screen; private IInputRoot _inputRoot; - private readonly Queue _inputThreadActions = new Queue(); private TouchDevice _touch = new TouchDevice(); private const string LibInput = nameof(Avalonia.LinuxFramebuffer) + "/" + nameof(Avalonia.LinuxFramebuffer.Input) + "/" + nameof(LibInput); private readonly RawEventGroupingThreadingHelper _inputQueue; From 4fe1977c2a93b0c8c2b21b837316a09813cf6549 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 15:49:17 +0100 Subject: [PATCH 041/213] feat(Windows): Address Rule CA1823 --- .../Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs index 966f996a91..08b3ee32fa 100644 --- a/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs +++ b/src/Windows/Avalonia.Win32/Interop/Automation/UiaCoreTypesApi.cs @@ -6,8 +6,6 @@ namespace Avalonia.Win32.Interop.Automation { internal static class UiaCoreTypesApi { - private const string StartListeningExportName = "SynchronizedInputPattern_StartListening"; - internal enum AutomationIdType { Property, From 64f84711edfda96185cde08597b63a0faa9e3cae Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 18:49:17 +0100 Subject: [PATCH 042/213] fix: Address review --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 1 - src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs | 1 - src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs | 2 -- src/Avalonia.Native/ClipboardImpl.cs | 3 +-- src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs | 6 ------ src/Windows/Avalonia.Win32/WindowImpl.cs | 2 -- 6 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 454678c64b..fe3445a30a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -48,7 +48,6 @@ namespace Avalonia.Controls private const string DATAGRID_elementColumnHeadersPresenterName = "PART_ColumnHeadersPresenter"; private const string DATAGRID_elementFrozenColumnScrollBarSpacerName = "PART_FrozenColumnScrollBarSpacer"; private const string DATAGRID_elementHorizontalScrollbarName = "PART_HorizontalScrollbar"; - private const string DATAGRID_elementRowHeadersPresenterName = "PART_RowHeadersPresenter"; private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader"; private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader"; private const string DATAGRID_elementBottomRightCornerHeaderName = "PART_BottomRightCorner"; diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 6473c9c051..252868847a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -34,7 +34,6 @@ namespace Avalonia.Controls } private const int DATAGRIDCOLUMNHEADER_resizeRegionWidth = 5; - private const double DATAGRIDCOLUMNHEADER_separatorThickness = 1; private const int DATAGRIDCOLUMNHEADER_columnsDragTreshold = 5; private bool _areHandlersSuspended; diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs index fe3ba0abf6..c42d21d126 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs @@ -18,8 +18,6 @@ namespace Avalonia.Controls.Primitives public class DataGridRowHeader : ContentControl { private const string DATAGRIDROWHEADER_elementRootName = "PART_Root"; - private const double DATAGRIDROWHEADER_separatorThickness = 1; - private Control _rootElement; public static readonly StyledProperty SeparatorBrushProperty = diff --git a/src/Avalonia.Native/ClipboardImpl.cs b/src/Avalonia.Native/ClipboardImpl.cs index 4ee590516b..9f1c8883aa 100644 --- a/src/Avalonia.Native/ClipboardImpl.cs +++ b/src/Avalonia.Native/ClipboardImpl.cs @@ -15,8 +15,7 @@ namespace Avalonia.Native private IAvnClipboard _native; private const string NSPasteboardTypeString = "public.utf8-plain-text"; private const string NSFilenamesPboardType = "NSFilenamesPboardType"; - private const string NSPasteboardTypeFileUrl = "public.file-url"; - + public ClipboardImpl(IAvnClipboard native) { _native = native; diff --git a/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs b/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs index 7e91d29019..c2e54c7ed7 100644 --- a/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs +++ b/src/Browser/Avalonia.Browser/BrowserNativeControlHost.cs @@ -55,12 +55,6 @@ namespace Avalonia.Browser private class Attachment : INativeControlHostControlTopLevelAttachment { - private const string InitializeWithChildHandleSymbol = "InitializeWithChildHandle"; - private const string AttachToSymbol = "AttachTo"; - private const string ShowInBoundsSymbol = "ShowInBounds"; - private const string HideWithSizeSymbol = "HideWithSize"; - private const string ReleaseChildSymbol = "ReleaseChild"; - private JSObject? _native; private BrowserNativeControlHost? _attachedTo; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 596b60a8cb..9e11959101 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1343,8 +1343,6 @@ namespace Avalonia.Win32 } private const int MF_BYCOMMAND = 0x0; - private const int MF_BYPOSITION = 0x400; - private const int MF_REMOVE = 0x1000; private const int MF_ENABLED = 0x0; private const int MF_GRAYED = 0x1; private const int MF_DISABLED = 0x2; From c96dcc68d800325d74ca734ba33fec31842d2292 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 18:35:25 +0100 Subject: [PATCH 043/213] fix: XML Comment --- src/Avalonia.Base/Platform/ITextShaperImpl.cs | 1 + src/Avalonia.Base/Rendering/DeferredRenderer.cs | 2 ++ src/Avalonia.Base/Rendering/ImmediateRenderer.cs | 2 ++ src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs | 1 + 4 files changed, 6 insertions(+) diff --git a/src/Avalonia.Base/Platform/ITextShaperImpl.cs b/src/Avalonia.Base/Platform/ITextShaperImpl.cs index ff91097eda..5ac8ba7768 100644 --- a/src/Avalonia.Base/Platform/ITextShaperImpl.cs +++ b/src/Avalonia.Base/Platform/ITextShaperImpl.cs @@ -13,6 +13,7 @@ namespace Avalonia.Platform /// Shapes the specified region within the text and returns a shaped buffer. /// /// The text buffer. + /// length of text /// Text shaper options to customize the shaping process. /// A shaped glyph run. ShapedBuffer ShapeText(CharacterBufferReference text, int length, TextShaperOptions options); diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs index 05aa1d1ea4..6a3a92fd09 100644 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Base/Rendering/DeferredRenderer.cs @@ -49,6 +49,8 @@ namespace Avalonia.Rendering /// /// The control to render. /// The render loop. + /// The target render factory. + /// /// The scene builder to use. Optional. /// The dispatcher to use. Optional. /// Lock object used before trying to access render target diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 1c797a5348..80e4daaecc 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -30,6 +30,8 @@ namespace Avalonia.Rendering /// Initializes a new instance of the class. /// /// The control to render. + /// The target render factory. + /// The render contex. public ImmediateRenderer(Visual root, Func renderTargetFactory, PlatformRenderInterfaceContextManager? renderContext = null) { diff --git a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs index b4dd754822..ebbb7a96dd 100644 --- a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs @@ -34,6 +34,7 @@ namespace Avalonia.Skia.Helpers /// Save Skia image to a stream. /// /// Image to save + /// The where write image. /// /// The optional quality for PNG compression. /// The quality value is interpreted from 0 - 100. If quality is null From 6d21b5b18efa8f26d9c5c70c8f063b3a09e3e475 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 12 Dec 2022 22:36:03 +0100 Subject: [PATCH 044/213] fix: Address review --- src/Avalonia.Base/Platform/ITextShaperImpl.cs | 2 +- src/Avalonia.Base/Rendering/DeferredRenderer.cs | 2 +- src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Platform/ITextShaperImpl.cs b/src/Avalonia.Base/Platform/ITextShaperImpl.cs index 5ac8ba7768..c3eb89ab1a 100644 --- a/src/Avalonia.Base/Platform/ITextShaperImpl.cs +++ b/src/Avalonia.Base/Platform/ITextShaperImpl.cs @@ -13,7 +13,7 @@ namespace Avalonia.Platform /// Shapes the specified region within the text and returns a shaped buffer. /// /// The text buffer. - /// length of text + /// The length of text. /// Text shaper options to customize the shaping process. /// A shaped glyph run. ShapedBuffer ShapeText(CharacterBufferReference text, int length, TextShaperOptions options); diff --git a/src/Avalonia.Base/Rendering/DeferredRenderer.cs b/src/Avalonia.Base/Rendering/DeferredRenderer.cs index 6a3a92fd09..3110b64c81 100644 --- a/src/Avalonia.Base/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Base/Rendering/DeferredRenderer.cs @@ -50,7 +50,7 @@ namespace Avalonia.Rendering /// The control to render. /// The render loop. /// The target render factory. - /// + /// The Platform Render Context. /// The scene builder to use. Optional. /// The dispatcher to use. Optional. /// Lock object used before trying to access render target diff --git a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs index ebbb7a96dd..4cb1430a3b 100644 --- a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs @@ -34,7 +34,7 @@ namespace Avalonia.Skia.Helpers /// Save Skia image to a stream. /// /// Image to save - /// The where write image. + /// The output stream to save the image. /// /// The optional quality for PNG compression. /// The quality value is interpreted from 0 - 100. If quality is null From 1e5c0b2911d5bace24b347897d93b60cc342f364 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 13 Dec 2022 10:36:48 +0100 Subject: [PATCH 045/213] feat: Linked AvaloniaResource Items --- .../GenerateAvaloniaResourcesTask.cs | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index fad6ad397b..65681a2d28 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -7,7 +7,7 @@ using System.Text; using Avalonia.Markup.Xaml.PortableXaml; using Avalonia.Utilities; using Microsoft.Build.Framework; -using SPath=System.IO.Path; +using SPath = System.IO.Path; namespace Avalonia.Build.Tasks { public class GenerateAvaloniaResourcesTask : ITask @@ -30,12 +30,17 @@ namespace Avalonia.Build.Tasks private byte[] _data; private string _sourcePath; - public Source(string relativePath, string root) + public Source(ITaskItem avaloniaResourceItem, string root) { root = SPath.GetFullPath(root); - Path = "/" + relativePath.Replace('\\', '/'); + var relativePath = avaloniaResourceItem.ItemSpec; _sourcePath = SPath.Combine(root, relativePath); Size = (int)new FileInfo(_sourcePath).Length; + var link = avaloniaResourceItem.GetMetadata("Link"); + var path = !string.IsNullOrEmpty(link) + ? link + : relativePath; + Path = "/" + path.Replace('\\', '/'); } public string SystemPath => _sourcePath ?? Path; @@ -65,8 +70,7 @@ namespace Avalonia.Build.Tasks List BuildResourceSources() => Resources.Select(r => { - - var src = new Source(r.ItemSpec, Root); + var src = new Source(r, Root); BuildEngine.LogMessage(FormattableString.Invariant($"avares -> name:{src.Path}, path: {src.SystemPath}, size:{src.Size}, ItemSpec:{r.ItemSpec}"), _reportImportance); return src; }).ToList(); @@ -93,15 +97,15 @@ namespace Avalonia.Build.Tasks ms.CopyTo(output); foreach (var s in sources) { - using(var input = s.Open()) + using (var input = s.Open()) input.CopyTo(output); } } private bool PreProcessXamlFiles(List sources) { - var typeToXamlIndex = new Dictionary(); - + var typeToXamlIndex = new Dictionary(); + foreach (var s in sources.ToArray()) { if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml")) @@ -111,7 +115,7 @@ namespace Avalonia.Build.Tasks { info = XamlFileInfo.Parse(s.ReadAsString()); } - catch(Exception e) + catch (Exception e) { BuildEngine.LogError(BuildEngineErrorCode.InvalidXAML, s.SystemPath, "File doesn't contain valid XAML: " + e); return false; @@ -121,7 +125,7 @@ namespace Avalonia.Build.Tasks { if (typeToXamlIndex.ContainsKey(info.XClass)) { - + BuildEngine.LogError(BuildEngineErrorCode.DuplicateXClass, s.SystemPath, $"Duplicate x:Class directive, {info.XClass} is already used in {typeToXamlIndex[info.XClass]}"); return false; From e3ab47247065c89d05935d9a3ec0d846bf75ab79 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 13 Dec 2022 12:17:10 +0000 Subject: [PATCH 046/213] set rounded corners when client is extended --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 8 ++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index a7cca5b0f3..58133c21f8 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -820,6 +820,14 @@ namespace Avalonia.Win32.Interop DWMWA_LAST }; + public enum DwmWindowCornerPreference : uint + { + DWMWCP_DEFAULT = 0, + DWMWCP_DONOTROUND, + DWMWCP_ROUND, + DWMWCP_ROUNDSMALL + } + public enum MapVirtualKeyMapTypes : uint { MAPVK_VK_TO_VSC = 0x00, diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9e11959101..ec4f9d8381 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1035,6 +1035,12 @@ namespace Avalonia.Win32 { var margins = UpdateExtendMargins(); DwmExtendFrameIntoClientArea(_hwnd, ref margins); + + unsafe + { + int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_ROUND; + DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int)); + } } else { From 987bc200f089a7bec91c1169f423cb211439b831 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 13 Dec 2022 12:21:02 +0000 Subject: [PATCH 047/213] restore attribute to defaul when client area is restored --- src/Windows/Avalonia.Win32/WindowImpl.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ec4f9d8381..54635d11cd 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1051,6 +1051,12 @@ namespace Avalonia.Win32 _extendedMargins = new Thickness(); Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout); + + unsafe + { + int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT; + DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int)); + } } if (!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && From 58b404a3ab25a8a7e6df0300f6356ce7f9e01dc6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Dec 2022 18:25:37 +0100 Subject: [PATCH 048/213] Added failing test for #7974. --- .../Selection/SelectionModelTests_Single.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs index 668af3b5d7..1b629be695 100644 --- a/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/Selection/SelectionModelTests_Single.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Specialized; +using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Selection; using Avalonia.Controls.Utils; @@ -1144,6 +1145,24 @@ namespace Avalonia.Controls.UnitTests.Selection Assert.Equal(new[] { "foo" }, target.SelectedItems); Assert.Equal(0, target.AnchorIndex); } + + [Fact] + public void SelectedItems_Indexer_Is_Correct() + { + // Issue #7974 + var target = CreateTarget(); + var raised = 0; + + target.SelectionChanged += (s, e) => + { + Assert.Equal("bar", e.SelectedItems.First()); + Assert.Equal("bar", e.SelectedItems[0]); + ++raised; + }; + + target.Select(1); + Assert.Equal(1, raised); + } } public class BatchUpdate From 467303099cc5ebcb8e091c508d888d8c66e690c8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 13 Dec 2022 18:32:52 +0100 Subject: [PATCH 049/213] Fix SelectedItems indexer. The index is an index into `Ranges`, not `Items`. Fixes #7974. --- src/Avalonia.Controls/Selection/SelectedItems.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Selection/SelectedItems.cs b/src/Avalonia.Controls/Selection/SelectedItems.cs index 4fbcfde438..ef642b7bdc 100644 --- a/src/Avalonia.Controls/Selection/SelectedItems.cs +++ b/src/Avalonia.Controls/Selection/SelectedItems.cs @@ -35,9 +35,9 @@ namespace Avalonia.Controls.Selection { return _owner.SelectedItem; } - else if (Items is object) + else if (Items is not null && Ranges is not null) { - return Items[index]; + return Items[IndexRange.GetAt(Ranges, index)]; } else { From 7deed3555674f46b1ef1bb2cc4e75043b3c0c383 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 14 Dec 2022 10:56:02 +0000 Subject: [PATCH 050/213] add refresh button to manager file chooser --- .../Controls/ManagedFileChooser.xaml | 7 +++++-- .../Controls/ManagedFileChooser.xaml | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml index d1707d0af2..63b3d46a41 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml @@ -153,9 +153,13 @@ - + @@ -322,7 +326,6 @@ diff --git a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml index ba1b35e2ee..945622f22f 100644 --- a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml +++ b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml @@ -9,17 +9,16 @@ - - From 6b07b9016198e444850c17b8c2e4421d139e1d4b Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Fri, 16 Dec 2022 16:38:53 +0300 Subject: [PATCH 066/213] DataGridValueConverter: fix processing of nullable values --- .../DataGridValueConverter.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs b/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs index 6144679b60..c823ee1b9c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs @@ -22,14 +22,18 @@ namespace Avalonia.Controls return DefaultValueConverter.Instance.Convert(value, targetType, parameter, culture); } - // This suppresses a warning saying that we should use String.IsNullOrEmpty instead of a string - // comparison, but in this case we want to explicitly check for Empty and not Null. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (targetType != null && targetType.IsNullableType()) { var strValue = value as string; - if (string.IsNullOrEmpty(strValue)) + + // This suppresses a warning saying that we should use String.IsNullOrEmpty instead of a string + // comparison, but in this case we want to explicitly check for Empty and not Null. +#pragma warning disable CA1820 + if (strValue == string.Empty) +#pragma warning restore CA1820 { return null; } From e0c2d5b63dcc92f5af44433e9e20c88cd9f0c8a4 Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Fri, 16 Dec 2022 18:29:34 +0300 Subject: [PATCH 067/213] DataGridColumn: change access modifier of OwningGrid property to protected internal --- src/Avalonia.Controls.DataGrid/DataGridColumn.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index d417c87746..2b715d6a9f 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -50,10 +50,13 @@ namespace Avalonia.Controls InheritsWidth = true; } - internal DataGrid OwningGrid + /// + /// Gets the control that contains this column. + /// + protected internal DataGrid OwningGrid { get; - set; + internal set; } internal int Index From 04db818a477471ace38b684c5557dc486d438134 Mon Sep 17 00:00:00 2001 From: 0xDB6 <120456350+0xDB6@users.noreply.github.com> Date: Fri, 16 Dec 2022 16:30:56 +0100 Subject: [PATCH 068/213] Defer execution of UnregisterClass to fix RWM atom table leak (#9700) --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 4 ++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 15 +++++++++------ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index a7cca5b0f3..f828b156da 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1409,7 +1409,7 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] public static extern bool TranslateMessage(ref MSG lpMsg); - [DllImport("user32.dll")] + [DllImport("user32.dll", CharSet = CharSet.Unicode)] public static extern bool UnregisterClass(string lpClassName, IntPtr hInstance); [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "SetWindowTextW")] diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index fe7d881f11..060366a8ac 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -10,6 +10,7 @@ using Avalonia.Controls.Remote; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.Win32.Automation; using Avalonia.Win32.Input; using Avalonia.Win32.Interop.Automation; @@ -106,6 +107,9 @@ namespace Avalonia.Win32 _touchDevice?.Dispose(); //Free other resources Dispose(); + + // Schedule cleanup of anything that requires window to be destroyed + Dispatcher.UIThread.Post(AfterCloseCleanup); return IntPtr.Zero; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9e11959101..fa4517dc92 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -643,12 +643,6 @@ namespace Avalonia.Win32 _hwnd = IntPtr.Zero; } - if (_className != null) - { - UnregisterClass(_className, GetModuleHandle(null)); - _className = null; - } - _framebuffer.Dispose(); } @@ -1144,6 +1138,15 @@ namespace Avalonia.Win32 } } + private void AfterCloseCleanup() + { + if (_className != null) + { + UnregisterClass(_className, GetModuleHandle(null)); + _className = null; + } + } + private void MaximizeWithoutCoveringTaskbar() { IntPtr monitor = MonitorFromWindow(_hwnd, MONITOR.MONITOR_DEFAULTTONEAREST); From c208e93281a8293a49a97d3f82ca8aea643fb971 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 17 Dec 2022 01:30:16 +0600 Subject: [PATCH 069/213] Logging --- .../Server/ServerCustomCompositionVisual.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs index 555972876d..227df87a87 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs @@ -12,7 +12,7 @@ internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, private bool _wantsNextAnimationFrameAfterTick; internal ServerCompositionCustomVisual(ServerCompositor compositor, CompositionCustomVisualHandler handler) : base(compositor) { - _handler = handler; + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); _handler.Attach(this); } @@ -29,7 +29,7 @@ internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, catch (Exception e) { Logger.TryGet(LogEventLevel.Error, LogArea.Visual) - ?.Log(_handler, $"Exception in {nameof(CompositionCustomVisualHandler)} {{0}}", e); + ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnMessage)} {{0}}", e); } } } @@ -68,7 +68,15 @@ internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) { - var context = new ImmediateDrawingContext(canvas, false); - _handler.OnRender(context); + using var context = new ImmediateDrawingContext(canvas, false); + try + { + _handler.OnRender(context); + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Error, LogArea.Visual) + ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnRender)} {{0}}", e); + } } } \ No newline at end of file From a5e018f77957d50f6ace60b034f7bf24c0092af0 Mon Sep 17 00:00:00 2001 From: Michal Tomecki Date: Fri, 16 Dec 2022 20:39:43 +0100 Subject: [PATCH 070/213] Changed X11Window/Position UpdateSizeHints to only fire if it's not already set --- src/Avalonia.X11/X11Window.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 62e4441066..890b49344c 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -962,8 +962,11 @@ namespace Avalonia.X11 get => _position ?? default; set { - _usePositioningFlags = true; - UpdateSizeHints(null); + if(!_usePositioningFlags) + { + _usePositioningFlags = true; + UpdateSizeHints(null); + } var changes = new XWindowChanges { From cfb8a896fa7010333d5c4e7ec82c34868ba5a639 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 17 Dec 2022 11:49:29 -0500 Subject: [PATCH 071/213] Modernize ComboBox properties --- src/Avalonia.Controls/ComboBox.cs | 71 ++++++++++++++++--------------- 1 file changed, 37 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f02df2e9c1..564a2e478e 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -95,10 +95,9 @@ namespace Avalonia.Controls { ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); - SelectedItemProperty.Changed.AddClassHandler((x, e) => x.SelectedItemChanged(e)); - KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); IsTextSearchEnabledProperty.OverrideDefaultValue(true); - IsDropDownOpenProperty.Changed.AddClassHandler((x, e) => x.DropdownChanged(e)); + + KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); } /// @@ -106,8 +105,8 @@ namespace Avalonia.Controls /// public bool IsDropDownOpen { - get { return _isDropDownOpen; } - set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } + get => _isDropDownOpen; + set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); } /// @@ -115,8 +114,8 @@ namespace Avalonia.Controls /// public double MaxDropDownHeight { - get { return GetValue(MaxDropDownHeightProperty); } - set { SetValue(MaxDropDownHeightProperty, value); } + get => GetValue(MaxDropDownHeightProperty); + set => SetValue(MaxDropDownHeightProperty, value); } /// @@ -124,8 +123,8 @@ namespace Avalonia.Controls /// protected object? SelectionBoxItem { - get { return _selectionBoxItem; } - set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } + get => _selectionBoxItem; + set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } /// @@ -133,8 +132,8 @@ namespace Avalonia.Controls /// public string? PlaceholderText { - get { return GetValue(PlaceholderTextProperty); } - set { SetValue(PlaceholderTextProperty, value); } + get => GetValue(PlaceholderTextProperty); + set => SetValue(PlaceholderTextProperty, value); } /// @@ -142,8 +141,8 @@ namespace Avalonia.Controls /// public IBrush? PlaceholderForeground { - get { return GetValue(PlaceholderForegroundProperty); } - set { SetValue(PlaceholderForegroundProperty, value); } + get => GetValue(PlaceholderForegroundProperty); + set => SetValue(PlaceholderForegroundProperty, value); } /// @@ -151,8 +150,8 @@ namespace Avalonia.Controls /// public ItemVirtualizationMode VirtualizationMode { - get { return GetValue(VirtualizationModeProperty); } - set { SetValue(VirtualizationModeProperty, value); } + get => GetValue(VirtualizationModeProperty); + set => SetValue(VirtualizationModeProperty, value); } /// @@ -160,8 +159,8 @@ namespace Avalonia.Controls /// public HorizontalAlignment HorizontalContentAlignment { - get { return GetValue(HorizontalContentAlignmentProperty); } - set { SetValue(HorizontalContentAlignmentProperty, value); } + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); } /// @@ -169,8 +168,8 @@ namespace Avalonia.Controls /// public VerticalAlignment VerticalContentAlignment { - get { return GetValue(VerticalContentAlignmentProperty); } - set { SetValue(VerticalContentAlignmentProperty, value); } + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); } /// @@ -182,6 +181,7 @@ namespace Avalonia.Controls ComboBoxItem.ContentTemplateProperty); } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -234,7 +234,7 @@ namespace Avalonia.Controls } // This part of code is needed just to acquire initial focus, subsequent focus navigation will be done by ItemsControl. else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 && - (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true) + (e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true) { var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); if (firstChild != null) @@ -304,12 +304,11 @@ namespace Avalonia.Controls e.Handled = true; } } + PseudoClasses.Set(pcPressed, false); base.OnPointerReleased(e); - } - /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { @@ -324,6 +323,22 @@ namespace Avalonia.Controls _popup.Closed += PopupClosed; } + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + if (change.Property == SelectedItemProperty) + { + UpdateSelectionBoxItem(change.NewValue); + TryFocusSelectedItem(); + } + else if (change.Property == IsDropDownOpenProperty) + { + PseudoClasses.Set(pcDropdownOpen, change.GetNewValue()); + } + + base.OnPropertyChanged(change); + } + protected override AutomationPeer OnCreateAutomationPeer() { return new ComboBoxAutomationPeer(this); @@ -384,12 +399,6 @@ namespace Avalonia.Controls } } - private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e) - { - UpdateSelectionBoxItem(e.NewValue); - TryFocusSelectedItem(); - } - private void TryFocusSelectedItem() { var selectedIndex = SelectedIndex; @@ -489,11 +498,5 @@ namespace Avalonia.Controls MoveSelection(NavigationDirection.Previous, WrapSelection); } } - - private void DropdownChanged(AvaloniaPropertyChangedEventArgs e) - { - bool newValue = e.GetNewValue(); - PseudoClasses.Set(pcDropdownOpen, newValue); - } } } From 9b24a5bed64d6d5695fa073f8468481339b74af4 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 17 Dec 2022 11:53:17 -0500 Subject: [PATCH 072/213] Remove duplicate OnKeyDown handler --- src/Avalonia.Controls/ComboBox.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 564a2e478e..f3179a4644 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -96,8 +96,6 @@ namespace Avalonia.Controls ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); IsTextSearchEnabledProperty.OverrideDefaultValue(true); - - KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); } /// From 53a78131f54440ff863804636545d79882d203fb Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 17 Dec 2022 13:23:26 -0500 Subject: [PATCH 073/213] Add DropDownOpened and DropDownClosed events to ComboBox --- src/Avalonia.Controls/ComboBox.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f3179a4644..c17b42b684 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -98,6 +98,16 @@ namespace Avalonia.Controls IsTextSearchEnabledProperty.OverrideDefaultValue(true); } + /// + /// Occurs after the drop-down (popup) list of the closes. + /// + public event EventHandler? DropDownClosed; + + /// + /// Occurs after the drop-down (popup) list of the opens. + /// + public event EventHandler? DropDownOpened; + /// /// Gets or sets a value indicating whether the dropdown is currently open. /// @@ -358,6 +368,8 @@ namespace Avalonia.Controls { Focus(); } + + DropDownClosed?.Invoke(this, EventArgs.Empty); } private void PopupOpened(object? sender, EventArgs e) @@ -387,6 +399,8 @@ namespace Avalonia.Controls } UpdateFlowDirection(); + + DropDownOpened?.Invoke(this, EventArgs.Empty); } private void IsVisibleChanged(bool isVisible) From 6497e49c267b28f365bbc735fa25de8b0609f610 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 18 Dec 2022 12:14:19 -0500 Subject: [PATCH 074/213] Open/Close ComboBox drop-down with Enter and Space --- src/Avalonia.Controls/ComboBox.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f02df2e9c1..a0085c92ca 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -213,7 +213,12 @@ namespace Avalonia.Controls IsDropDownOpen = false; e.Handled = true; } - else if (IsDropDownOpen && e.Key == Key.Enter) + else if (!IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) + { + IsDropDownOpen = true; + e.Handled = true; + } + else if (IsDropDownOpen && (e.Key == Key.Enter || e.Key == Key.Space)) { SelectFocusedItem(); IsDropDownOpen = false; From 59d7874b1dc397987c65ac159a40dfbccfe93679 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 20 Dec 2022 21:42:36 -0500 Subject: [PATCH 075/213] Replace System.Reactive with internal extensions --- build/Base.props | 1 + src/Avalonia.Base/Animation/Animation.cs | 3 +- .../Animation/AnimationInstance`1.cs | 5 +- .../Animation/AnimatorKeyFrame.cs | 2 +- .../Animation/Animators/Animator`1.cs | 2 - .../Animation/Animators/ColorAnimator.cs | 2 +- .../Animation/Animators/TransformAnimator.cs | 2 +- src/Avalonia.Base/Animation/Clock.cs | 1 + src/Avalonia.Base/Animation/CrossFade.cs | 4 +- src/Avalonia.Base/Avalonia.Base.csproj | 7 +- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 176 +++----- src/Avalonia.Base/AvaloniaProperty`1.cs | 6 +- src/Avalonia.Base/ClassBindingManager.cs | 1 + .../Collections/AvaloniaListExtensions.cs | 2 +- .../NotifyCollectionChangedExtensions.cs | 1 - .../Controls/NameScopeLocator.cs | 2 +- src/Avalonia.Base/Data/BindingOperations.cs | 11 +- .../Data/Core/AvaloniaPropertyAccessorNode.cs | 1 - .../Data/Core/BindingExpression.cs | 6 +- .../Data/Core/ExpressionObserver.cs | 17 +- .../Data/Core/IndexerNodeBase.cs | 61 ++- .../Core/Plugins/ObservableStreamPlugin.cs | 65 +-- .../Data/Core/Plugins/TaskStreamPlugin.cs | 5 +- src/Avalonia.Base/Data/Core/StreamNode.cs | 2 +- src/Avalonia.Base/Data/IndexerBinding.cs | 9 +- src/Avalonia.Base/Data/IndexerDescriptor.cs | 5 +- src/Avalonia.Base/Data/InstancedBinding.cs | 52 +-- src/Avalonia.Base/Input/Gestures.cs | 1 + src/Avalonia.Base/Input/InputElement.cs | 1 + src/Avalonia.Base/Input/InputManager.cs | 8 +- src/Avalonia.Base/Input/MouseDevice.cs | 1 + .../Input/TextInput/InputMethodManager.cs | 2 +- .../Interactivity/InteractiveExtensions.cs | 3 +- .../Interactivity/RoutedEvent.cs | 6 +- src/Avalonia.Base/Layout/Layoutable.cs | 2 +- src/Avalonia.Base/Media/Brush.cs | 1 + src/Avalonia.Base/Media/DashStyle.cs | 1 + .../Media/ExperimentalAcrylicMaterial.cs | 1 + src/Avalonia.Base/Media/Geometry.cs | 1 + src/Avalonia.Base/Media/GradientBrush.cs | 1 + src/Avalonia.Base/Media/MatrixTransform.cs | 1 + src/Avalonia.Base/Media/RotateTransform.cs | 1 + src/Avalonia.Base/Media/ScaleTransform.cs | 1 + src/Avalonia.Base/Media/SkewTransform.cs | 1 + src/Avalonia.Base/Media/TranslateTransform.cs | 1 + .../PropertyStore/BindingEntryBase.cs | 2 +- .../Reactive/AnonymousObserver.cs | 62 +++ src/Avalonia.Base/Reactive/CombinedSubject.cs | 23 + .../Reactive/CompositeDisposable.cs | 427 ++++++++++++++++++ src/Avalonia.Base/Reactive/Disposable.cs | 98 ++++ src/Avalonia.Base/Reactive/DisposableMixin.cs | 37 ++ .../Reactive/IAvaloniaSubject.cs | 8 + .../Reactive/LightweightObservableBase.cs | 8 +- .../Reactive/LightweightSubject.cs | 30 ++ src/Avalonia.Base/Reactive/Observable.cs | 238 ++++++++++ src/Avalonia.Base/Reactive/ObservableEx.cs | 37 -- .../Reactive/Operators/CombineLatest.cs | 374 +++++++++++++++ src/Avalonia.Base/Reactive/Operators/Sink.cs | 109 +++++ .../Reactive/Operators/Switch.cs | 144 ++++++ .../PlatformRenderInterfaceContextManager.cs | 2 +- .../Rendering/SceneGraph/VisualNode.cs | 2 +- .../Rendering/UiThreadRenderTimer.cs | 2 +- src/Avalonia.Base/Styling/StyleInstance.cs | 6 +- .../Threading/DispatcherTimer.cs | 2 +- .../Utilities/IWeakSubscriber.cs | 18 - src/Avalonia.Base/Utilities/WeakObservable.cs | 60 --- src/Avalonia.Base/Visual.cs | 75 +-- .../Avalonia.Controls.ColorPicker.csproj | 1 - .../ColorSpectrum/ColorSpectrum.cs | 1 + .../Avalonia.Controls.DataGrid.csproj | 1 - src/Avalonia.Controls.DataGrid/DataGrid.cs | 1 + .../DataGridBoundColumn.cs | 5 - src/Avalonia.Controls.DataGrid/DataGridRow.cs | 11 +- .../DataGridRowGroupHeader.cs | 2 +- .../Utils/CellEditBinding.cs | 14 +- src/Avalonia.Controls/Application.cs | 3 - .../AutoCompleteBox/AutoCompleteBox.cs | 2 +- .../Avalonia.Controls.csproj | 1 - src/Avalonia.Controls/Button.cs | 1 + src/Avalonia.Controls/ButtonSpinner.cs | 3 +- .../CalendarDatePicker/CalendarDatePicker.cs | 2 +- src/Avalonia.Controls/Canvas.cs | 3 - .../Chrome/CaptionButtons.cs | 11 +- src/Avalonia.Controls/Chrome/TitleBar.cs | 4 +- src/Avalonia.Controls/ComboBox.cs | 2 +- src/Avalonia.Controls/ComboBoxItem.cs | 13 +- src/Avalonia.Controls/ContextMenu.cs | 1 + src/Avalonia.Controls/ControlExtensions.cs | 5 +- src/Avalonia.Controls/DataValidationErrors.cs | 2 +- src/Avalonia.Controls/DefinitionBase.cs | 1 + .../ExperimentalAcrylicBorder.cs | 1 + src/Avalonia.Controls/Flyouts/FlyoutBase.cs | 1 + src/Avalonia.Controls/HotkeyManager.cs | 1 + .../LayoutTransformControl.cs | 6 +- src/Avalonia.Controls/MenuItem.cs | 7 +- .../Mixins/DisposableMixin.cs | 38 -- .../Mixins/SelectableMixin.cs | 2 +- src/Avalonia.Controls/NativeMenu.Export.cs | 3 +- src/Avalonia.Controls/NativeMenuBar.cs | 1 + src/Avalonia.Controls/NativeMenuItem.cs | 1 + .../Notifications/NotificationCard.cs | 41 +- .../WindowNotificationManager.cs | 2 +- .../NumericUpDown/NumericUpDown.cs | 1 + src/Avalonia.Controls/Panel.cs | 1 + .../Platform/DefaultMenuInteractionHandler.cs | 2 +- .../Platform/InProcessDragSource.cs | 28 +- .../Platform/PlatformManager.cs | 2 +- .../Presenters/CarouselPresenter.cs | 2 +- .../Presenters/ItemVirtualizer.cs | 2 +- .../Presenters/ScrollContentPresenter.cs | 4 +- .../Primitives/AccessText.cs | 3 +- .../Primitives/AdornerLayer.cs | 1 + .../Primitives/OverlayPopupHost.cs | 2 +- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- src/Avalonia.Controls/ProgressBar.cs | 1 + .../PullToRefresh/RefreshContainer.cs | 1 + .../PullToRefresh/RefreshVisualizer.cs | 2 +- src/Avalonia.Controls/RadioButton.cs | 1 + .../Repeater/ViewportManager.cs | 8 - src/Avalonia.Controls/ScrollViewer.cs | 2 +- src/Avalonia.Controls/Shapes/Shape.cs | 1 + src/Avalonia.Controls/Spinner.cs | 1 + .../SplitButton/SplitButton.cs | 1 + src/Avalonia.Controls/SplitView.cs | 1 + .../Templates/FuncDataTemplate.cs | 2 +- src/Avalonia.Controls/TextBox.cs | 2 +- src/Avalonia.Controls/ToolTip.cs | 1 + src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Controls/TrayIcon.cs | 2 +- src/Avalonia.Controls/TreeView.cs | 2 +- src/Avalonia.Controls/Utils/AncestorFinder.cs | 18 +- src/Avalonia.Controls/Window.cs | 4 +- src/Avalonia.Controls/WindowBase.cs | 5 - .../Avalonia.DesignerSupport.csproj | 1 - src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- .../Diagnostics/DevTools.cs | 4 +- .../Diagnostics/ViewModels/LogicalTreeNode.cs | 2 +- .../Diagnostics/ViewModels/VisualTreeNode.cs | 2 +- .../Internal/ManagedFileChooserViewModel.cs | 9 +- .../DBusIme/Fcitx/FcitxX11TextInputMethod.cs | 1 - src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 2 +- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 2 +- .../LinuxMountedVolumeInfoListener.cs | 22 +- .../AvaloniaHeadlessPlatform.cs | 2 +- .../HeadlessPlatformThreadingInterface.cs | 2 +- src/Avalonia.Native/IAvnMenu.cs | 2 +- src/Avalonia.Native/IAvnMenuItem.cs | 2 +- .../MacOSMountedVolumeInfoProvider.cs | 21 +- src/Avalonia.OpenGL/Egl/EglContext.cs | 2 +- src/Avalonia.OpenGL/Egl/EglDisplay.cs | 2 +- src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs | 3 +- src/Avalonia.ReactiveUI/Attributes.cs | 1 - src/Avalonia.ReactiveUI/AutoSuspendHelper.cs | 2 +- .../AvaloniaObjectReactiveExtensions.cs | 110 +++++ .../AvaloniaScheduler.cs | 3 +- .../Avalonia.Themes.Fluent.csproj | 1 - .../Avalonia.Themes.Simple.csproj | 1 - src/Avalonia.X11/Glx/GlxContext.cs | 2 +- src/Avalonia.X11/X11Window.cs | 1 + .../Avalonia.Markup.Xaml.csproj | 1 - .../FindVisualAncestorNode.cs | 1 + .../ObservableStreamPlugin.cs | 2 +- .../PropertyInfoAccessorFactory.cs | 1 + .../CompiledBindings/TaskStreamPlugin.cs | 5 +- .../Templates/TemplateContent.cs | 1 - .../Avalonia.Markup/Avalonia.Markup.csproj | 1 - src/Markup/Avalonia.Markup/Data/Binding.cs | 7 - .../Avalonia.Markup/Data/BindingBase.cs | 10 +- .../Avalonia.Markup/Data/MultiBinding.cs | 2 +- .../Avalonia.Markup/Data/TemplateBinding.cs | 3 +- .../Parsers/ExpressionObserverBuilder.cs | 3 +- .../Markup/Parsers/Nodes/ElementNameNode.cs | 1 + .../Markup/Parsers/Nodes/FindAncestorNode.cs | 1 + .../Markup/Parsers/Nodes/StringIndexerNode.cs | 2 +- .../Avalonia.Skia/FramebufferRenderTarget.cs | 2 +- .../Gpu/OpenGl/GlRenderTarget.cs | 2 +- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 2 +- .../Avalonia.Direct2D1.csproj | 1 - src/Windows/Avalonia.Win32/ClipboardImpl.cs | 2 +- .../Avalonia.Win32/OpenGl/WglContext.cs | 2 +- .../Win32NativeToManagedMenuExporter.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 2 +- .../Composition/WinUiCompositedWindow.cs | 2 +- .../WindowsMountedVolumeInfoListener.cs | 22 +- .../AvaloniaObjectTests_Binding.cs | 3 +- .../Core/ExpressionObserverTests_Lifetime.cs | 6 +- .../Core/ExpressionObserverTests_Property.cs | 6 +- .../Styling/StyleActivatorExtensions.cs | 5 +- .../Avalonia.IntegrationTests.Appium.csproj | 1 + .../Data/BindingTests.cs | 6 +- .../Data/BindingTests_DataValidation.cs | 6 +- .../Data/MultiBindingTests.cs | 4 + .../AvaloniaObjectTests_GetSubject.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 4 - .../Avalonia.UnitTests/UnitTestApplication.cs | 1 - 195 files changed, 2144 insertions(+), 735 deletions(-) create mode 100644 src/Avalonia.Base/Reactive/AnonymousObserver.cs create mode 100644 src/Avalonia.Base/Reactive/CombinedSubject.cs create mode 100644 src/Avalonia.Base/Reactive/CompositeDisposable.cs create mode 100644 src/Avalonia.Base/Reactive/Disposable.cs create mode 100644 src/Avalonia.Base/Reactive/DisposableMixin.cs create mode 100644 src/Avalonia.Base/Reactive/IAvaloniaSubject.cs create mode 100644 src/Avalonia.Base/Reactive/LightweightSubject.cs create mode 100644 src/Avalonia.Base/Reactive/Observable.cs delete mode 100644 src/Avalonia.Base/Reactive/ObservableEx.cs create mode 100644 src/Avalonia.Base/Reactive/Operators/CombineLatest.cs create mode 100644 src/Avalonia.Base/Reactive/Operators/Sink.cs create mode 100644 src/Avalonia.Base/Reactive/Operators/Switch.cs delete mode 100644 src/Avalonia.Base/Utilities/IWeakSubscriber.cs delete mode 100644 src/Avalonia.Base/Utilities/WeakObservable.cs delete mode 100644 src/Avalonia.Controls/Mixins/DisposableMixin.cs create mode 100644 src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs rename src/{Avalonia.Base/Threading => Avalonia.ReactiveUI}/AvaloniaScheduler.cs (98%) rename tests/{Avalonia.Base.UnitTests => Avalonia.ReactiveUI.UnitTests}/AvaloniaObjectTests_GetSubject.cs (95%) diff --git a/build/Base.props b/build/Base.props index 100c9088cd..588da8433a 100644 --- a/build/Base.props +++ b/build/Base.props @@ -1,6 +1,7 @@  + diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs index 06087cdd6a..d62acc0d52 100644 --- a/src/Avalonia.Base/Animation/Animation.cs +++ b/src/Avalonia.Base/Animation/Animation.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading; using System.Threading.Tasks; diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 0881fde988..682629c801 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -1,11 +1,8 @@ using System; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Animation.Animators; -using Avalonia.Animation.Utils; using Avalonia.Data; -using Avalonia.Reactive; -using JetBrains.Annotations; namespace Avalonia.Animation { diff --git a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs index 3168a67d79..dcce75b31c 100644 --- a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs @@ -63,7 +63,7 @@ namespace Avalonia.Animation } else { - return this.Bind(ValueProperty, ObservableEx.SingleValue(value).ToBinding(), targetControl); + return this.Bind(ValueProperty, Observable.SingleValue(value).ToBinding(), targetControl); } } diff --git a/src/Avalonia.Base/Animation/Animators/Animator`1.cs b/src/Avalonia.Base/Animation/Animators/Animator`1.cs index b5d1feb4a7..2db890bd0a 100644 --- a/src/Avalonia.Base/Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Base/Animation/Animators/Animator`1.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Animation.Utils; using Avalonia.Collections; using Avalonia.Data; diff --git a/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs b/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs index 72add21d69..7be974b9e5 100644 --- a/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs +++ b/src/Avalonia.Base/Animation/Animators/ColorAnimator.cs @@ -2,7 +2,7 @@ // and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp). using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Logging; using Avalonia.Media; diff --git a/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs index e12ca722f9..8040fb595b 100644 --- a/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Base/Animation/Animators/TransformAnimator.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Logging; using Avalonia.Media; using Avalonia.Media.Transformation; diff --git a/src/Avalonia.Base/Animation/Clock.cs b/src/Avalonia.Base/Animation/Clock.cs index 5afd2ae705..f2bce9d3a5 100644 --- a/src/Avalonia.Base/Animation/Clock.cs +++ b/src/Avalonia.Base/Animation/Clock.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; namespace Avalonia.Animation { diff --git a/src/Avalonia.Base/Animation/CrossFade.cs b/src/Avalonia.Base/Animation/CrossFade.cs index a229bc7ce6..640d6456a3 100644 --- a/src/Avalonia.Base/Animation/CrossFade.cs +++ b/src/Avalonia.Base/Animation/CrossFade.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading; using System.Threading.Tasks; using Avalonia.Animation.Easings; @@ -108,7 +108,7 @@ namespace Avalonia.Animation } var tasks = new List(); - using (var disposables = new CompositeDisposable()) + using (var disposables = new CompositeDisposable(1)) { if (to != null) { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 0d3da66f7a..d40fe1e6f8 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -14,7 +14,6 @@ - @@ -35,6 +34,12 @@ + + + + + + diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 867d6215a5..7b17b9152d 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -1,10 +1,6 @@ using System; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; -using Avalonia.Data; using Avalonia.Reactive; +using Avalonia.Data; namespace Avalonia { @@ -127,108 +123,6 @@ namespace Avalonia property ?? throw new ArgumentNullException(nameof(property))); } - /// - /// Gets a subject for an . - /// - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject GetSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create( - Observer.Create(x => o.SetValue(property, x, priority)), - o.GetObservable(property)); - } - - /// - /// Gets a subject for an . - /// - /// The property type. - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject GetSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create( - Observer.Create(x => o.SetValue(property, x, priority)), - o.GetObservable(property)); - } - - /// - /// Gets a subject for a . - /// - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject> GetBindingSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create>( - Observer.Create>(x => - { - if (x.HasValue) - { - o.SetValue(property, x.Value, priority); - } - }), - o.GetBindingObservable(property)); - } - - /// - /// Gets a subject for a . - /// - /// The property type. - /// The object. - /// The property. - /// - /// The priority with which binding values are written to the object. - /// - /// - /// An which can be used for two-way binding to/from the - /// property. - /// - public static ISubject> GetBindingSubject( - this AvaloniaObject o, - AvaloniaProperty property, - BindingPriority priority = BindingPriority.LocalValue) - { - return Subject.Create>( - Observer.Create>(x => - { - if (x.HasValue) - { - o.SetValue(property, x.Value, priority); - } - }), - o.GetBindingObservable(property)); - } - /// /// Binds an to an observable. /// @@ -407,13 +301,7 @@ namespace Avalonia Action action) where TTarget : AvaloniaObject { - return observable.Subscribe(e => - { - if (e.Sender is TTarget target) - { - action(target, e); - } - }); + return observable.Subscribe(new ClassHandlerObserver(action)); } /// @@ -431,13 +319,7 @@ namespace Avalonia this IObservable> observable, Action> action) where TTarget : AvaloniaObject { - return observable.Subscribe(e => - { - if (e.Sender is TTarget target) - { - action(target, e); - } - }); + return observable.Subscribe(new ClassHandlerObserver(action)); } private class BindingAdaptor : IBinding @@ -458,5 +340,57 @@ namespace Avalonia return InstancedBinding.OneWay(_source); } } + + private class ClassHandlerObserver : IObserver> + { + private readonly Action> _action; + + public ClassHandlerObserver(Action> action) + { + _action = action; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(AvaloniaPropertyChangedEventArgs value) + { + if (value.Sender is TTarget target) + { + _action(target, value); + } + } + } + + private class ClassHandlerObserver : IObserver + { + private readonly Action _action; + + public ClassHandlerObserver(Action action) + { + _action = action; + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void OnNext(AvaloniaPropertyChangedEventArgs value) + { + if (value.Sender is TTarget target) + { + _action(target, value); + } + } + } } } diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index 53444ee475..f8c062a176 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Subjects; using Avalonia.Data; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia @@ -12,7 +12,7 @@ namespace Avalonia /// The value type of the property. public abstract class AvaloniaProperty : AvaloniaProperty { - private readonly Subject> _changed; + private readonly LightweightSubject> _changed; /// /// Initializes a new instance of the class. @@ -28,7 +28,7 @@ namespace Avalonia Action? notifying = null) : base(name, typeof(TValue), ownerType, metadata, notifying) { - _changed = new Subject>(); + _changed = new LightweightSubject>(); } /// diff --git a/src/Avalonia.Base/ClassBindingManager.cs b/src/Avalonia.Base/ClassBindingManager.cs index 589b9b2d01..a9726cb86e 100644 --- a/src/Avalonia.Base/ClassBindingManager.cs +++ b/src/Avalonia.Base/ClassBindingManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Avalonia.Data; +using Avalonia.Reactive; namespace Avalonia { diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs index c4684960d6..2178577eb7 100644 --- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs +++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs @@ -3,7 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; -using System.Reactive.Disposables; +using Avalonia.Reactive; namespace Avalonia.Collections { diff --git a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs index 689fcc89a4..68863ea257 100644 --- a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs +++ b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Specialized; -using System.Reactive.Linq; using Avalonia.Reactive; using Avalonia.Utilities; diff --git a/src/Avalonia.Base/Controls/NameScopeLocator.cs b/src/Avalonia.Base/Controls/NameScopeLocator.cs index f0ce7f8a5b..371931f971 100644 --- a/src/Avalonia.Base/Controls/NameScopeLocator.cs +++ b/src/Avalonia.Base/Controls/NameScopeLocator.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Controls diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs index ceb3f71285..0b737dd959 100644 --- a/src/Avalonia.Base/Data/BindingOperations.cs +++ b/src/Avalonia.Base/Data/BindingOperations.cs @@ -1,6 +1,5 @@ using System; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; namespace Avalonia.Data { @@ -46,15 +45,15 @@ namespace Avalonia.Data throw new InvalidOperationException("InstancedBinding does not contain an observable."); return target.Bind(property, binding.Observable, binding.Priority); case BindingMode.TwoWay: + if (binding.Observable is null) + throw new InvalidOperationException("InstancedBinding does not contain an observable."); if (binding.Subject is null) throw new InvalidOperationException("InstancedBinding does not contain a subject."); return new TwoWayBindingDisposable( - target.Bind(property, binding.Subject, binding.Priority), + target.Bind(property, binding.Observable, binding.Priority), target.GetObservable(property).Subscribe(binding.Subject)); case BindingMode.OneTime: - var source = binding.Subject ?? binding.Observable; - - if (source != null) + if (binding.Observable is {} source) { // Perf: Avoid allocating closure in the outer scope. var targetCopy = target; diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs index e340966983..536c14dcf9 100644 --- a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs @@ -1,5 +1,4 @@ using System; -using System.Reactive.Linq; using Avalonia.Reactive; namespace Avalonia.Data.Core diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index f60b4722d9..55caf8070e 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -1,11 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Reactive.Linq; -using System.Reactive.Subjects; +using Avalonia.Reactive; using Avalonia.Data.Converters; using Avalonia.Logging; -using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Data.Core @@ -15,7 +13,7 @@ namespace Avalonia.Data.Core /// that are sent and received. /// [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] - public class BindingExpression : LightweightObservableBase, ISubject, IDescription + public class BindingExpression : LightweightObservableBase, IAvaloniaSubject, IDescription { private readonly ExpressionObserver _inner; private readonly Type _targetType; diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 2151c100e5..d1d7b98562 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; -using System.Reactive; -using System.Reactive.Linq; using Avalonia.Data.Core.Parsers; using Avalonia.Data.Core.Plugins; using Avalonia.Reactive; @@ -99,14 +97,14 @@ namespace Avalonia.Data.Core /// /// A function which gets the root object. /// The expression. - /// An observable which triggers a re-read of the getter. + /// An observable which triggers a re-read of the getter. Generic argument value is not used. /// /// A description of the expression. /// public ExpressionObserver( Func rootGetter, ExpressionNode node, - IObservable update, + IObservable update, string? description) { Description = description; @@ -164,7 +162,7 @@ namespace Avalonia.Data.Core /// /// A function which gets the root object. /// The expression. - /// An observable which triggers a re-read of the getter. + /// An observable which triggers a re-read of the getter. Generic argument value is not used. /// Whether or not to track data validation /// /// A description of the expression. If null, 's string representation will be used. @@ -173,7 +171,7 @@ namespace Avalonia.Data.Core public static ExpressionObserver Create( Func rootGetter, Expression> expression, - IObservable update, + IObservable update, bool enableDataValidation = false, string? description = null) { @@ -296,9 +294,10 @@ namespace Avalonia.Data.Core if (_root is IObservable observable) { _rootSubscription = observable.Subscribe( - x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), - x => PublishCompleted(), - () => PublishCompleted()); + new AnonymousObserver( + x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null), + x => PublishCompleted(), + PublishCompleted)); } else { diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs index a808827896..2fad96701d 100644 --- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs +++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs @@ -1,48 +1,47 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; -using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Data.Core { - public abstract class IndexerNodeBase : SettableNode + public abstract class IndexerNodeBase : SettableNode, + IWeakEventSubscriber, + IWeakEventSubscriber { - private IDisposable? _subscription; - protected override void StartListeningCore(WeakReference reference) { reference.TryGetTarget(out var target); - var incc = target as INotifyCollectionChanged; - var inpc = target as INotifyPropertyChanged; - var inputs = new List>(); - - if (incc != null) + if (target is INotifyCollectionChanged incc) { - inputs.Add(WeakObservable.FromEventPattern( - incc, WeakEvents.CollectionChanged) - .Where(x => ShouldUpdate(x.Sender, x.EventArgs)) - .Select(_ => GetValue(target))); + WeakEvents.CollectionChanged.Subscribe(incc, this); } - if (inpc != null) + if (target is INotifyPropertyChanged inpc) { - inputs.Add(WeakObservable.FromEventPattern( - inpc, WeakEvents.PropertyChanged) - .Where(x => ShouldUpdate(x.Sender, x.EventArgs)) - .Select(_ => GetValue(target))); + WeakEvents.PropertyChanged.Subscribe(inpc, this); } - - _subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged); + + ValueChanged(GetValue(target)); } protected override void StopListeningCore() { - _subscription?.Dispose(); + if (Target.TryGetTarget(out var target)) + { + if (target is INotifyCollectionChanged incc) + { + WeakEvents.CollectionChanged.Unsubscribe(incc, this); + } + + if (target is INotifyPropertyChanged inpc) + { + WeakEvents.PropertyChanged.Unsubscribe(inpc, this); + } + } } protected abstract object? GetValue(object? target); @@ -83,5 +82,21 @@ namespace Avalonia.Data.Core } protected abstract bool ShouldUpdate(object? sender, PropertyChangedEventArgs e); + + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs e) + { + if (ShouldUpdate(sender, e)) + { + ValueChanged(GetValue(sender)); + } + } + + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, PropertyChangedEventArgs e) + { + if (ShouldUpdate(sender, e)) + { + ValueChanged(GetValue(sender)); + } + } } } diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index ebee4586db..b40628fd35 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Reflection; namespace Avalonia.Data.Core.Plugins @@ -12,8 +12,15 @@ namespace Avalonia.Data.Core.Plugins [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] public class ObservableStreamPlugin : IStreamPlugin { - static MethodInfo? observableSelect; + private static MethodInfo? s_observableGeneric; + private static MethodInfo? s_observableSelect; + [DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicProperties, "Avalonia.Data.Core.Plugins.ObservableStreamPlugin", "Avalonia.Base")] + public ObservableStreamPlugin() + { + + } + /// /// Checks whether this plugin handles the specified value. /// @@ -54,56 +61,32 @@ namespace Avalonia.Data.Core.Plugins x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0]; - // Get the Observable.Select method. - var select = GetObservableSelect(sourceType); - - // Make a Box<> delegate of the correct type. - var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object)); - var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)! - .MakeGenericMethod(sourceType) - .CreateDelegate(funcType); + // Get the BoxObservable method. + var select = GetBoxObservable(sourceType); - // Call Observable.Select(target, box); + // Call BoxObservable(target); return (IObservable)select.Invoke( null, - new object[] { target, box })!; + new[] { target })!; } [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] - private static MethodInfo GetObservableSelect(Type source) + private static MethodInfo GetBoxObservable(Type source) { - return GetObservableSelect().MakeGenericMethod(source, typeof(object)); + return (s_observableGeneric ??= GetBoxObservable()).MakeGenericMethod(source); } - private static MethodInfo GetObservableSelect() + [RequiresUnreferencedCode(TrimmingMessages.StreamPluginRequiresUnreferencedCodeMessage)] + private static MethodInfo GetBoxObservable() { - if (observableSelect == null) - { - observableSelect = typeof(Observable).GetRuntimeMethods().First(x => - { - if (x.Name == nameof(Observable.Select) && - x.ContainsGenericParameters && - x.GetGenericArguments().Length == 2) - { - var parameters = x.GetParameters(); - - if (parameters.Length == 2 && - parameters[0].ParameterType.IsConstructedGenericType && - parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) && - parameters[1].ParameterType.IsConstructedGenericType && - parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)) - { - return true; - } - } - - return false; - }); - } - - return observableSelect; + return s_observableSelect + ??= typeof(ObservableStreamPlugin).GetMethod(nameof(BoxObservable), BindingFlags.Static | BindingFlags.NonPublic) + ?? throw new InvalidOperationException("BoxObservable method was not found."); } - private static object? Box(T value) => (object?)value; + private static IObservable BoxObservable(IObservable source) + { + return source.Select(v => (object?)v); + } } } diff --git a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs index 5203aa9f57..715f4604cf 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs @@ -1,9 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Reflection; using System.Threading.Tasks; +using Avalonia.Reactive; namespace Avalonia.Data.Core.Plugins { @@ -50,7 +49,7 @@ namespace Avalonia.Data.Core.Plugins case TaskStatus.Faulted: return HandleCompleted(task); default: - var subject = new Subject(); + var subject = new LightweightSubject(); task.ContinueWith( x => HandleCompleted(task).Subscribe(subject), TaskScheduler.FromCurrentSynchronizationContext()) diff --git a/src/Avalonia.Base/Data/Core/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs index 6dc6d07184..ba18a2173b 100644 --- a/src/Avalonia.Base/Data/Core/StreamNode.cs +++ b/src/Avalonia.Base/Data/Core/StreamNode.cs @@ -1,7 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; using Avalonia.Data.Core.Plugins; +using Avalonia.Reactive; namespace Avalonia.Data.Core { diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs index fcd179b9b2..83ef8f76b4 100644 --- a/src/Avalonia.Base/Data/IndexerBinding.cs +++ b/src/Avalonia.Base/Data/IndexerBinding.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Data +using Avalonia.Reactive; + +namespace Avalonia.Data { public class IndexerBinding : IBinding { @@ -22,7 +24,10 @@ object? anchor = null, bool enableDataValidation = false) { - return new InstancedBinding(Source.GetSubject(Property), Mode, BindingPriority.LocalValue); + var subject = new CombinedSubject( + new AnonymousObserver(x => Source.SetValue(Property, x, BindingPriority.LocalValue)), + Source.GetObservable(Property)); + return new InstancedBinding(subject, Mode, BindingPriority.LocalValue); } } } diff --git a/src/Avalonia.Base/Data/IndexerDescriptor.cs b/src/Avalonia.Base/Data/IndexerDescriptor.cs index c823630d3c..3eb5a6ef12 100644 --- a/src/Avalonia.Base/Data/IndexerDescriptor.cs +++ b/src/Avalonia.Base/Data/IndexerDescriptor.cs @@ -1,12 +1,11 @@ using System; -using System.Reactive; namespace Avalonia.Data { /// /// Holds a description of a binding for 's [] operator. /// - public class IndexerDescriptor : ObservableBase, IDescription + public class IndexerDescriptor : IObservable, IDescription { /// /// Gets or sets the binding mode. @@ -104,7 +103,7 @@ namespace Avalonia.Data } /// - protected override IDisposable SubscribeCore(IObserver observer) + public IDisposable Subscribe(IObserver observer) { if (SourceObservable is null && Source is null) throw new InvalidOperationException("Cannot subscribe to IndexerDescriptor."); diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index a349486bf8..a60c1d72ec 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Data { @@ -14,28 +14,7 @@ namespace Avalonia.Data /// public class InstancedBinding { - /// - /// Initializes a new instance of the class. - /// - /// The binding source. - /// The binding mode. - /// The priority of the binding. - /// - /// This constructor can be used to create any type of binding and as such requires an - /// as the binding source because this is the only binding - /// source which can be used for all binding modes. If you wish to create an instance with - /// something other than a subject, use one of the static creation methods on this class. - /// - public InstancedBinding(ISubject subject, BindingMode mode, BindingPriority priority) - { - Contract.Requires(subject != null); - - Mode = mode; - Priority = priority; - Value = subject; - } - - private InstancedBinding(object? value, BindingMode mode, BindingPriority priority) + internal InstancedBinding(object? value, BindingMode mode, BindingPriority priority) { Mode = mode; Priority = priority; @@ -63,9 +42,14 @@ namespace Avalonia.Data public IObservable? Observable => Value as IObservable; /// - /// Gets the as a subject. + /// Gets the as an observer. /// - public ISubject? Subject => Value as ISubject; + public IObserver? Observer => Value as IObserver; + + /// + /// Gets the as an subject. + /// + internal IAvaloniaSubject? Subject => Value as IAvaloniaSubject; /// /// Creates a new one-time binding with a fixed value. @@ -113,30 +97,34 @@ namespace Avalonia.Data /// /// Creates a new one-way to source binding. /// - /// The binding source. + /// The binding source. /// The priority of the binding. /// An instance. public static InstancedBinding OneWayToSource( - ISubject subject, + IObserver observer, BindingPriority priority = BindingPriority.LocalValue) { - _ = subject ?? throw new ArgumentNullException(nameof(subject)); + _ = observer ?? throw new ArgumentNullException(nameof(observer)); - return new InstancedBinding(subject, BindingMode.OneWayToSource, priority); + return new InstancedBinding(observer, BindingMode.OneWayToSource, priority); } /// /// Creates a new two-way binding. /// - /// The binding source. + /// The binding source. + /// The binding source. /// The priority of the binding. /// An instance. public static InstancedBinding TwoWay( - ISubject subject, + IObservable observable, + IObserver observer, BindingPriority priority = BindingPriority.LocalValue) { - _ = subject ?? throw new ArgumentNullException(nameof(subject)); + _ = observable ?? throw new ArgumentNullException(nameof(observable)); + _ = observer ?? throw new ArgumentNullException(nameof(observer)); + var subject = new CombinedSubject(observer, observable); return new InstancedBinding(subject, BindingMode.TwoWay, priority); } diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 1ea88fe824..8de0afbc1f 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Interactivity; using Avalonia.Platform; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Input diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index fa755277cc..59b45cec48 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -7,6 +7,7 @@ using Avalonia.Data; using Avalonia.Input.GestureRecognizers; using Avalonia.Input.TextInput; using Avalonia.Interactivity; +using Avalonia.Reactive; using Avalonia.VisualTree; #nullable enable diff --git a/src/Avalonia.Base/Input/InputManager.cs b/src/Avalonia.Base/Input/InputManager.cs index f604ff8e74..e87665fd54 100644 --- a/src/Avalonia.Base/Input/InputManager.cs +++ b/src/Avalonia.Base/Input/InputManager.cs @@ -1,6 +1,6 @@ using System; -using System.Reactive.Subjects; using Avalonia.Input.Raw; +using Avalonia.Reactive; namespace Avalonia.Input { @@ -10,9 +10,9 @@ namespace Avalonia.Input /// public class InputManager : IInputManager { - private readonly Subject _preProcess = new Subject(); - private readonly Subject _process = new Subject(); - private readonly Subject _postProcess = new Subject(); + private readonly LightweightSubject _preProcess = new(); + private readonly LightweightSubject _process = new(); + private readonly LightweightSubject _postProcess = new(); /// /// Gets the global instance of the input manager. diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 9b17e336d3..e1c42c4ead 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Avalonia.Reactive; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Utilities; diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index 8a3f5b968e..9b5668bf98 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -1,5 +1,5 @@ using System; -using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Input.TextInput { diff --git a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs index b12cf64fdf..5264b96679 100644 --- a/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs +++ b/src/Avalonia.Base/Interactivity/InteractiveExtensions.cs @@ -1,6 +1,5 @@ using System; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; namespace Avalonia.Interactivity { diff --git a/src/Avalonia.Base/Interactivity/RoutedEvent.cs b/src/Avalonia.Base/Interactivity/RoutedEvent.cs index 7046871538..79a4dc60e4 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEvent.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Interactivity { @@ -13,8 +13,8 @@ namespace Avalonia.Interactivity public class RoutedEvent { - private readonly Subject<(object, RoutedEventArgs)> _raised = new Subject<(object, RoutedEventArgs)>(); - private readonly Subject _routeFinished = new Subject(); + private readonly LightweightSubject<(object, RoutedEventArgs)> _raised = new(); + private readonly LightweightSubject _routeFinished = new(); public RoutedEvent( string name, diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index d185148894..9a899ba084 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -1,6 +1,6 @@ using System; using Avalonia.Logging; -using Avalonia.Styling; +using Avalonia.Reactive; using Avalonia.VisualTree; #nullable enable diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index 4138f1c891..a1afb458c6 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using Avalonia.Animation; using Avalonia.Animation.Animators; using Avalonia.Media.Immutable; +using Avalonia.Reactive; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs index 9c30b6f872..92b07191e6 100644 --- a/src/Avalonia.Base/Media/DashStyle.cs +++ b/src/Avalonia.Base/Media/DashStyle.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using Avalonia.Animation; using Avalonia.Collections; using Avalonia.Media.Immutable; +using Avalonia.Reactive; #nullable enable diff --git a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs index 0e485d0db8..b7a186dde4 100644 --- a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index 2019f54c70..dd162c1e71 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Platform; +using Avalonia.Reactive; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Media/GradientBrush.cs b/src/Avalonia.Base/Media/GradientBrush.cs index 83cdaa1694..e1654a01b2 100644 --- a/src/Avalonia.Base/Media/GradientBrush.cs +++ b/src/Avalonia.Base/Media/GradientBrush.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using Avalonia.Animation.Animators; using Avalonia.Collections; using Avalonia.Metadata; +using Avalonia.Reactive; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Media/MatrixTransform.cs b/src/Avalonia.Base/Media/MatrixTransform.cs index c61acb730c..22f39b1ee2 100644 --- a/src/Avalonia.Base/Media/MatrixTransform.cs +++ b/src/Avalonia.Base/Media/MatrixTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Base/Media/RotateTransform.cs b/src/Avalonia.Base/Media/RotateTransform.cs index 3bd409149c..ab3ea6770f 100644 --- a/src/Avalonia.Base/Media/RotateTransform.cs +++ b/src/Avalonia.Base/Media/RotateTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Base/Media/ScaleTransform.cs b/src/Avalonia.Base/Media/ScaleTransform.cs index d4c1a7f993..86163df471 100644 --- a/src/Avalonia.Base/Media/ScaleTransform.cs +++ b/src/Avalonia.Base/Media/ScaleTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Base/Media/SkewTransform.cs b/src/Avalonia.Base/Media/SkewTransform.cs index 066f5371c3..2de2d734c5 100644 --- a/src/Avalonia.Base/Media/SkewTransform.cs +++ b/src/Avalonia.Base/Media/SkewTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Base/Media/TranslateTransform.cs b/src/Avalonia.Base/Media/TranslateTransform.cs index 0f910f3600..75528a4261 100644 --- a/src/Avalonia.Base/Media/TranslateTransform.cs +++ b/src/Avalonia.Base/Media/TranslateTransform.cs @@ -1,4 +1,5 @@ using System; +using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Media diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index b5ac5c817d..11dc80ef8f 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Data; using Avalonia.Threading; diff --git a/src/Avalonia.Base/Reactive/AnonymousObserver.cs b/src/Avalonia.Base/Reactive/AnonymousObserver.cs new file mode 100644 index 0000000000..c2e02ae879 --- /dev/null +++ b/src/Avalonia.Base/Reactive/AnonymousObserver.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; + +namespace Avalonia.Reactive; + +internal class AnonymousObserver : IObserver +{ + private static readonly Action ThrowsOnError = ex => throw ex; + private static readonly Action NoOpCompleted = () => { }; + private readonly Action _onNext; + private readonly Action _onError; + private readonly Action _onCompleted; + + public AnonymousObserver(TaskCompletionSource tcs) + { + if (tcs is null) + { + throw new ArgumentNullException(nameof(tcs)); + } + + _onNext = tcs.SetResult; + _onError = tcs.SetException; + _onCompleted = NoOpCompleted; + } + + public AnonymousObserver(Action onNext, Action onError, Action onCompleted) + { + _onNext = onNext ?? throw new ArgumentNullException(nameof(onNext)); + _onError = onError ?? throw new ArgumentNullException(nameof(onError)); + _onCompleted = onCompleted ?? throw new ArgumentNullException(nameof(onCompleted)); + } + + public AnonymousObserver(Action onNext) + : this(onNext, ThrowsOnError, NoOpCompleted) + { + } + + public AnonymousObserver(Action onNext, Action onError) + : this(onNext, onError, NoOpCompleted) + { + } + + public AnonymousObserver(Action onNext, Action onCompleted) + : this(onNext, ThrowsOnError, onCompleted) + { + } + + public void OnCompleted() + { + _onCompleted.Invoke(); + } + + public void OnError(Exception error) + { + _onError.Invoke(error); + } + + public void OnNext(T value) + { + _onNext.Invoke(value); + } +} diff --git a/src/Avalonia.Base/Reactive/CombinedSubject.cs b/src/Avalonia.Base/Reactive/CombinedSubject.cs new file mode 100644 index 0000000000..dc6153133d --- /dev/null +++ b/src/Avalonia.Base/Reactive/CombinedSubject.cs @@ -0,0 +1,23 @@ +using System; + +namespace Avalonia.Reactive; + +internal class CombinedSubject : IAvaloniaSubject +{ + private readonly IObserver _observer; + private readonly IObservable _observable; + + public CombinedSubject(IObserver observer, IObservable observable) + { + _observer = observer; + _observable = observable; + } + + public void OnCompleted() => _observer.OnCompleted(); + + public void OnError(Exception error) => _observer.OnError(error); + + public void OnNext(T value) => _observer.OnNext(value); + + public IDisposable Subscribe(IObserver observer) => _observable.Subscribe(observer); +} diff --git a/src/Avalonia.Base/Reactive/CompositeDisposable.cs b/src/Avalonia.Base/Reactive/CompositeDisposable.cs new file mode 100644 index 0000000000..4952686ad8 --- /dev/null +++ b/src/Avalonia.Base/Reactive/CompositeDisposable.cs @@ -0,0 +1,427 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; + +namespace Avalonia.Reactive; + +internal sealed class CompositeDisposable : ICollection, IDisposable +{ + private readonly object _gate = new object(); + private bool _disposed; + private List _disposables; + private int _count; + private const int ShrinkThreshold = 64; + + /// + /// Initializes a new instance of the class with the specified number of disposables. + /// + /// The number of disposables that the new CompositeDisposable can initially store. + /// is less than zero. + public CompositeDisposable(int capacity) + { + if (capacity < 0) + { + throw new ArgumentOutOfRangeException(nameof(capacity)); + } + + _disposables = new List(capacity); + } + + /// + /// Initializes a new instance of the class from a group of disposables. + /// + /// Disposables that will be disposed together. + /// is null. + /// Any of the disposables in the collection is null. + public CompositeDisposable(params IDisposable[] disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + _disposables = ToList(disposables); + + // _count can be read by other threads and thus should be properly visible + // also releases the _disposables contents so it becomes thread-safe + Volatile.Write(ref _count, _disposables.Count); + } + + /// + /// Initializes a new instance of the class from a group of disposables. + /// + /// Disposables that will be disposed together. + /// is null. + /// Any of the disposables in the collection is null. + public CompositeDisposable(IList disposables) + { + if (disposables == null) + { + throw new ArgumentNullException(nameof(disposables)); + } + + _disposables = ToList(disposables); + + // _count can be read by other threads and thus should be properly visible + // also releases the _disposables contents so it becomes thread-safe + Volatile.Write(ref _count, _disposables.Count); + } + + private static List ToList(IEnumerable disposables) + { + var capacity = disposables switch + { + IDisposable[] a => a.Length, + ICollection c => c.Count, + _ => 12 + }; + + var list = new List(capacity); + + // do the copy and null-check in one step to avoid a + // second loop for just checking for null items + foreach (var d in disposables) + { + if (d == null) + { + throw new ArgumentException("Disposables can't contain null", nameof(disposables)); + } + + list.Add(d); + } + + return list; + } + + /// + /// Gets the number of disposables contained in the . + /// + public int Count => Volatile.Read(ref _count); + + /// + /// Adds a disposable to the or disposes the disposable if the is disposed. + /// + /// Disposable to add. + /// is null. + public void Add(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + if (!_disposed) + { + _disposables.Add(item); + + // If read atomically outside the lock, it should be written atomically inside + // the plain read on _count is fine here because manipulation always happens + // from inside a lock. + Volatile.Write(ref _count, _count + 1); + return; + } + } + + item.Dispose(); + } + + /// + /// Removes and disposes the first occurrence of a disposable from the . + /// + /// Disposable to remove. + /// true if found; false otherwise. + /// is null. + public bool Remove(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + // this composite was already disposed and if the item was in there + // it has been already removed/disposed + if (_disposed) + { + return false; + } + + // + // List doesn't shrink the size of the underlying array but does collapse the array + // by copying the tail one position to the left of the removal index. We don't need + // index-based lookup but only ordering for sequential disposal. So, instead of spending + // cycles on the Array.Copy imposed by Remove, we use a null sentinel value. We also + // do manual Swiss cheese detection to shrink the list if there's a lot of holes in it. + // + + // read fields as infrequently as possible + var current = _disposables; + + var i = current.IndexOf(item); + if (i < 0) + { + // not found, just return + return false; + } + + current[i] = null; + + if (current.Capacity > ShrinkThreshold && _count < current.Capacity / 2) + { + var fresh = new List(current.Capacity / 2); + + foreach (var d in current) + { + if (d != null) + { + fresh.Add(d); + } + } + + _disposables = fresh; + } + + // make sure the Count property sees an atomic update + Volatile.Write(ref _count, _count - 1); + } + + // if we get here, the item was found and removed from the list + // just dispose it and report success + + item.Dispose(); + + return true; + } + + /// + /// Disposes all disposables in the group and removes them from the group. + /// + public void Dispose() + { + List? currentDisposables = null; + + lock (_gate) + { + if (!_disposed) + { + currentDisposables = _disposables; + + // nulling out the reference is faster no risk to + // future Add/Remove because _disposed will be true + // and thus _disposables won't be touched again. + _disposables = null!; // NB: All accesses are guarded by _disposed checks. + + Volatile.Write(ref _count, 0); + Volatile.Write(ref _disposed, true); + } + } + + if (currentDisposables != null) + { + foreach (var d in currentDisposables) + { + d?.Dispose(); + } + } + } + + /// + /// Removes and disposes all disposables from the , but does not dispose the . + /// + public void Clear() + { + IDisposable?[] previousDisposables; + + lock (_gate) + { + // disposed composites are always clear + if (_disposed) + { + return; + } + + var current = _disposables; + + previousDisposables = current.ToArray(); + current.Clear(); + + Volatile.Write(ref _count, 0); + } + + foreach (var d in previousDisposables) + { + d?.Dispose(); + } + } + + /// + /// Determines whether the contains a specific disposable. + /// + /// Disposable to search for. + /// true if the disposable was found; otherwise, false. + /// is null. + public bool Contains(IDisposable item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + lock (_gate) + { + if (_disposed) + { + return false; + } + + return _disposables.Contains(item); + } + } + + /// + /// Copies the disposables contained in the to an array, starting at a particular array index. + /// + /// Array to copy the contained disposables to. + /// Target index at which to copy the first disposable of the group. + /// is null. + /// is less than zero. -or - is larger than or equal to the array length. + public void CopyTo(IDisposable[] array, int arrayIndex) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + if (arrayIndex < 0 || arrayIndex >= array.Length) + { + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + lock (_gate) + { + // disposed composites are always empty + if (_disposed) + { + return; + } + + if (arrayIndex + _count > array.Length) + { + // there is not enough space beyond arrayIndex + // to accommodate all _count disposables in this composite + throw new ArgumentOutOfRangeException(nameof(arrayIndex)); + } + + var i = arrayIndex; + + foreach (var d in _disposables) + { + if (d != null) + { + array[i++] = d; + } + } + } + } + + /// + /// Always returns false. + /// + public bool IsReadOnly => false; + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator to iterate over the disposables. + public IEnumerator GetEnumerator() + { + lock (_gate) + { + if (_disposed || _count == 0) + { + return EmptyEnumerator; + } + + // the copy is unavoidable but the creation + // of an outer IEnumerable is avoidable + return new CompositeEnumerator(_disposables.ToArray()); + } + } + + /// + /// Returns an enumerator that iterates through the . + /// + /// An enumerator to iterate over the disposables. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + /// Gets a value that indicates whether the object is disposed. + /// + public bool IsDisposed => Volatile.Read(ref _disposed); + + /// + /// An empty enumerator for the + /// method to avoid allocation on disposed or empty composites. + /// + private static readonly CompositeEnumerator EmptyEnumerator = + new CompositeEnumerator(Array.Empty()); + + /// + /// An enumerator for an array of disposables. + /// + private sealed class CompositeEnumerator : IEnumerator + { + private readonly IDisposable?[] _disposables; + private int _index; + + public CompositeEnumerator(IDisposable?[] disposables) + { + _disposables = disposables; + _index = -1; + } + + public IDisposable Current => _disposables[_index]!; // NB: _index is only advanced to non-null positions. + + object IEnumerator.Current => _disposables[_index]!; + + public void Dispose() + { + // Avoid retention of the referenced disposables + // beyond the lifecycle of the enumerator. + // Not sure if this happens by default to + // generic array enumerators though. + var disposables = _disposables; + Array.Clear(disposables, 0, disposables.Length); + } + + public bool MoveNext() + { + var disposables = _disposables; + + for (;;) + { + var idx = ++_index; + + if (idx >= disposables.Length) + { + return false; + } + + // inlined that filter for null elements + if (disposables[idx] != null) + { + return true; + } + } + } + + public void Reset() + { + _index = -1; + } + } +} diff --git a/src/Avalonia.Base/Reactive/Disposable.cs b/src/Avalonia.Base/Reactive/Disposable.cs new file mode 100644 index 0000000000..17b0d01db5 --- /dev/null +++ b/src/Avalonia.Base/Reactive/Disposable.cs @@ -0,0 +1,98 @@ +using System; +using System.Threading; + +namespace Avalonia.Reactive; + +/// +/// Provides a set of static methods for creating objects. +/// +internal static class Disposable +{ + /// + /// Represents a disposable that does nothing on disposal. + /// + private sealed class EmptyDisposable : IDisposable + { + public static readonly EmptyDisposable Instance = new(); + + private EmptyDisposable() + { + } + + public void Dispose() + { + // no op + } + } + + internal sealed class AnonymousDisposable : IDisposable + { + private volatile Action? _dispose; + public AnonymousDisposable(Action dispose) + { + _dispose = dispose; + } + public bool IsDisposed => _dispose == null; + public void Dispose() + { + Interlocked.Exchange(ref _dispose, null)?.Invoke(); + } + } + + internal sealed class AnonymousDisposable : IDisposable + { + private TState _state; + private volatile Action? _dispose; + + public AnonymousDisposable(TState state, Action dispose) + { + _state = state; + _dispose = dispose; + } + + public bool IsDisposed => _dispose == null; + public void Dispose() + { + Interlocked.Exchange(ref _dispose, null)?.Invoke(_state); + _state = default!; + } + } + + /// + /// Gets the disposable that does nothing when disposed. + /// + public static IDisposable Empty => EmptyDisposable.Instance; + + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(Action dispose) + { + if (dispose == null) + { + throw new ArgumentNullException(nameof(dispose)); + } + + return new AnonymousDisposable(dispose); + } + + /// + /// Creates a disposable object that invokes the specified action when disposed. + /// + /// The state to be passed to the action. + /// Action to run during the first call to . The action is guaranteed to be run at most once. + /// The disposable object that runs the given action upon disposal. + /// is null. + public static IDisposable Create(TState state, Action dispose) + { + if (dispose == null) + { + throw new ArgumentNullException(nameof(dispose)); + } + + return new AnonymousDisposable(state, dispose); + } +} diff --git a/src/Avalonia.Base/Reactive/DisposableMixin.cs b/src/Avalonia.Base/Reactive/DisposableMixin.cs new file mode 100644 index 0000000000..478312fa47 --- /dev/null +++ b/src/Avalonia.Base/Reactive/DisposableMixin.cs @@ -0,0 +1,37 @@ +using System; +using Avalonia.Reactive; + +namespace Avalonia.Reactive; + +/// +/// Extension methods associated with the IDisposable interface. +/// +internal static class DisposableMixin +{ + /// + /// Ensures the provided disposable is disposed with the specified . + /// + /// + /// The type of the disposable. + /// + /// + /// The disposable we are going to want to be disposed by the CompositeDisposable. + /// + /// + /// The to which will be added. + /// + /// + /// The disposable. + /// + public static T DisposeWith(this T item, CompositeDisposable compositeDisposable) + where T : IDisposable + { + if (compositeDisposable is null) + { + throw new ArgumentNullException(nameof(compositeDisposable)); + } + + compositeDisposable.Add(item); + return item; + } +} diff --git a/src/Avalonia.Base/Reactive/IAvaloniaSubject.cs b/src/Avalonia.Base/Reactive/IAvaloniaSubject.cs new file mode 100644 index 0000000000..272e2190b8 --- /dev/null +++ b/src/Avalonia.Base/Reactive/IAvaloniaSubject.cs @@ -0,0 +1,8 @@ +using System; + +namespace Avalonia.Reactive; + +internal interface IAvaloniaSubject : IObserver, IObservable /*, ISubject */ +{ + +} diff --git a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs index 9a3ab89b62..263109972f 100644 --- a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs +++ b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive; -using System.Reactive.Disposables; using System.Threading; using Avalonia.Threading; @@ -12,7 +10,7 @@ namespace Avalonia.Reactive /// /// The observable type. /// - /// is rather heavyweight in terms of allocations and memory + /// ObservableBase{T} is rather heavyweight in terms of allocations and memory /// usage. This class provides a more lightweight base for some internal observable types /// in the Avalonia framework. /// @@ -21,11 +19,13 @@ namespace Avalonia.Reactive private Exception? _error; private List>? _observers = new List>(); + public bool HasObservers => _observers?.Count > 0; + public IDisposable Subscribe(IObserver observer) { _ = observer ?? throw new ArgumentNullException(nameof(observer)); - Dispatcher.UIThread.VerifyAccess(); + //Dispatcher.UIThread.VerifyAccess(); var first = false; diff --git a/src/Avalonia.Base/Reactive/LightweightSubject.cs b/src/Avalonia.Base/Reactive/LightweightSubject.cs new file mode 100644 index 0000000000..5663fd66d3 --- /dev/null +++ b/src/Avalonia.Base/Reactive/LightweightSubject.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Avalonia.Threading; + +namespace Avalonia.Reactive; + +internal class LightweightSubject : LightweightObservableBase, IAvaloniaSubject +{ + public void OnCompleted() + { + PublishCompleted(); + } + + public void OnError(Exception error) + { + PublishError(error); + } + + public void OnNext(T value) + { + PublishNext(value); + } + + protected override void Initialize() { } + + protected override void Deinitialize() { } +} diff --git a/src/Avalonia.Base/Reactive/Observable.cs b/src/Avalonia.Base/Reactive/Observable.cs new file mode 100644 index 0000000000..5191efa910 --- /dev/null +++ b/src/Avalonia.Base/Reactive/Observable.cs @@ -0,0 +1,238 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Avalonia.Reactive.Operators; +using Avalonia.Threading; + +namespace Avalonia.Reactive; + +/// +/// Provides common observable methods as a replacement for the Rx framework. +/// +internal static class Observable +{ + public static IObservable Create(Func, IDisposable> subscribe) + { + return new CreateWithDisposableObservable(subscribe); + } + + public static IDisposable Subscribe(this IObservable source, Action action) + { + return source.Subscribe(new AnonymousObserver(action)); + } + + public static IObservable Select(this IObservable source, Func selector) + { + return Create(obs => + { + return source.Subscribe(new AnonymousObserver( + input => + { + TResult value; + try + { + value = selector(input); + } + catch (Exception ex) + { + obs.OnError(ex); + return; + } + + obs.OnNext(value); + }, obs.OnError, obs.OnCompleted)); + }); + } + + public static IObservable Where(this IObservable source, Func predicate) + { + return Create(obs => + { + return source.Subscribe(new AnonymousObserver( + input => + { + bool shouldRun; + try + { + shouldRun = predicate(input); + } + catch (Exception ex) + { + obs.OnError(ex); + return; + } + if (shouldRun) + { + obs.OnNext(input); + } + }, obs.OnError, obs.OnCompleted)); + }); + } + + public static IObservable Switch( + this IObservable> sources) + { + return new Switch(sources); + } + + public static IObservable CombineLatest( + this IObservable first, IObservable second, + Func resultSelector) + { + return new CombineLatest(first, second, resultSelector); + } + + public static IObservable CombineLatest( + this IEnumerable> inputs) + { + return new CombineLatest(inputs, items => items); + } + + public static IObservable Skip(this IObservable source, int skipCount) + { + if (skipCount <= 0) + { + throw new ArgumentException("Skip count must be bigger than zero", nameof(skipCount)); + } + + return Create(obs => + { + var remaining = skipCount; + return source.Subscribe(new AnonymousObserver( + input => + { + if (remaining <= 0) + { + obs.OnNext(input); + } + else + { + remaining--; + } + }, obs.OnError, obs.OnCompleted)); + }); + } + + public static IObservable Take(this IObservable source, int takeCount) + { + if (takeCount <= 0) + { + return Empty(); + } + + return Create(obs => + { + var remaining = takeCount; + IDisposable? sub = null; + sub = source.Subscribe(new AnonymousObserver( + input => + { + if (remaining > 0) + { + --remaining; + obs.OnNext(input); + + if (remaining == 0) + { + sub?.Dispose(); + obs.OnCompleted(); + } + } + }, obs.OnError, obs.OnCompleted)); + return sub; + }); + } + + public static IObservable FromEventPattern(Action addHandler, Action removeHandler) + { + return Create(observer => + { + var handler = new Action(observer.OnNext); + var converted = new EventHandler((_, args) => handler(args)); + addHandler(converted); + + return Disposable.Create(() => removeHandler(converted)); + }); + } + + public static IObservable Return(T value) + { + return new ReturnImpl(value); + } + + public static IObservable Empty() + { + return EmptyImpl.Instance; + } + + /// + /// Returns an observable that fires once with the specified value and never completes. + /// + /// The type of the value. + /// The value. + /// The observable. + public static IObservable SingleValue(T value) + { + return new SingleValueImpl(value); + } + + private sealed class SingleValueImpl : IObservable + { + private readonly T _value; + + public SingleValueImpl(T value) + { + _value = value; + } + public IDisposable Subscribe(IObserver observer) + { + observer.OnNext(_value); + return Disposable.Empty; + } + } + + private sealed class ReturnImpl : IObservable + { + private readonly T _value; + + public ReturnImpl(T value) + { + _value = value; + } + public IDisposable Subscribe(IObserver observer) + { + observer.OnNext(_value); + observer.OnCompleted(); + return Disposable.Empty; + } + } + + internal sealed class EmptyImpl : IObservable + { + internal static readonly IObservable Instance = new EmptyImpl(); + + private EmptyImpl() { } + + public IDisposable Subscribe(IObserver observer) + { + observer.OnCompleted(); + return Disposable.Empty; + } + } + + private sealed class CreateWithDisposableObservable : IObservable + { + private readonly Func, IDisposable> _subscribe; + + public CreateWithDisposableObservable(Func, IDisposable> subscribe) + { + _subscribe = subscribe; + } + + public IDisposable Subscribe(IObserver observer) + { + return _subscribe(observer); + } + } +} diff --git a/src/Avalonia.Base/Reactive/ObservableEx.cs b/src/Avalonia.Base/Reactive/ObservableEx.cs deleted file mode 100644 index 1cea568c88..0000000000 --- a/src/Avalonia.Base/Reactive/ObservableEx.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Reactive.Disposables; - -namespace Avalonia.Reactive -{ - /// - /// Provides common observable methods not found in standard Rx framework. - /// - public static class ObservableEx - { - /// - /// Returns an observable that fires once with the specified value and never completes. - /// - /// The type of the value. - /// The value. - /// The observable. - public static IObservable SingleValue(T value) - { - return new SingleValueImpl(value); - } - - private class SingleValueImpl : IObservable - { - private T _value; - - public SingleValueImpl(T value) - { - _value = value; - } - public IDisposable Subscribe(IObserver observer) - { - observer.OnNext(_value); - return Disposable.Empty; - } - } - } -} diff --git a/src/Avalonia.Base/Reactive/Operators/CombineLatest.cs b/src/Avalonia.Base/Reactive/Operators/CombineLatest.cs new file mode 100644 index 0000000000..aa95ceec35 --- /dev/null +++ b/src/Avalonia.Base/Reactive/Operators/CombineLatest.cs @@ -0,0 +1,374 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Avalonia.Reactive.Operators; + +// Code based on https://github.com/dotnet/reactive/blob/main/Rx.NET/Source/src/System.Reactive/Linq/Observable/CombineLatest.cs + +internal sealed class CombineLatest : IObservable +{ + private readonly IObservable _first; + private readonly IObservable _second; + private readonly Func _resultSelector; + + public CombineLatest(IObservable first, IObservable second, + Func resultSelector) + { + _first = first; + _second = second; + _resultSelector = resultSelector; + } + + public IDisposable Subscribe(IObserver observer) + { + var sink = new _(_resultSelector, observer); + sink.Run(_first, _second); + return sink; + } + + internal sealed class _ : IdentitySink + { + private readonly Func _resultSelector; + private readonly object _gate = new object(); + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + _firstDisposable = null!; + _secondDisposable = null!; + } + + private IDisposable _firstDisposable; + private IDisposable _secondDisposable; + + public void Run(IObservable first, IObservable second) + { + var fstO = new FirstObserver(this); + var sndO = new SecondObserver(this); + + fstO.SetOther(sndO); + sndO.SetOther(fstO); + + _firstDisposable = first.Subscribe(fstO); + _secondDisposable = second.Subscribe(sndO); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _firstDisposable.Dispose(); + _secondDisposable.Dispose(); + } + + base.Dispose(disposing); + } + + private sealed class FirstObserver : IObserver + { + private readonly _ _parent; + private SecondObserver _other; + + public FirstObserver(_ parent) + { + _parent = parent; + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(SecondObserver other) { _other = other; } + + public bool HasValue { get; private set; } + public TFirst? Value { get; private set; } + public bool Done { get; private set; } + + public void OnNext(TFirst value) + { + lock (_parent._gate) + { + HasValue = true; + Value = value; + + if (_other.HasValue) + { + TResult res; + try + { + res = _parent._resultSelector(value, _other.Value!); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._firstDisposable.Dispose(); + } + } + } + } + + private sealed class SecondObserver : IObserver + { + private readonly _ _parent; + private FirstObserver _other; + + public SecondObserver(_ parent) + { + _parent = parent; + _other = default!; // NB: Will be set by SetOther. + } + + public void SetOther(FirstObserver other) { _other = other; } + + public bool HasValue { get; private set; } + public TSecond? Value { get; private set; } + public bool Done { get; private set; } + + public void OnNext(TSecond value) + { + lock (_parent._gate) + { + HasValue = true; + Value = value; + + if (_other.HasValue) + { + TResult res; + try + { + res = _parent._resultSelector(_other.Value!, value); + } + catch (Exception ex) + { + _parent.ForwardOnError(ex); + return; + } + + _parent.ForwardOnNext(res); + } + else if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + _parent.ForwardOnError(error); + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Done = true; + + if (_other.Done) + { + _parent.ForwardOnCompleted(); + } + else + { + _parent._secondDisposable.Dispose(); + } + } + } + } + } +} + +internal sealed class CombineLatest : IObservable +{ + private readonly IEnumerable> _sources; + private readonly Func _resultSelector; + + public CombineLatest(IEnumerable> sources, Func resultSelector) + { + _sources = sources; + _resultSelector = resultSelector; + } + + public IDisposable Subscribe(IObserver observer) + { + var sink = new _(_resultSelector, observer); + sink.Run(_sources); + return sink; + } + + internal sealed class _ : IdentitySink + { + private readonly object _gate = new object(); + private readonly Func _resultSelector; + + public _(Func resultSelector, IObserver observer) + : base(observer) + { + _resultSelector = resultSelector; + + // NB: These will be set in Run before getting used. + _hasValue = null!; + _values = null!; + _isDone = null!; + _subscriptions = null!; + } + + private bool[] _hasValue; + private bool _hasValueAll; + private TSource[] _values; + private bool[] _isDone; + private IDisposable[] _subscriptions; + + public void Run(IEnumerable> sources) + { + var srcs = sources.ToArray(); + + var N = srcs.Length; + + _hasValue = new bool[N]; + _hasValueAll = false; + + _values = new TSource[N]; + + _isDone = new bool[N]; + + _subscriptions = new IDisposable[N]; + + for (var i = 0; i < N; i++) + { + var j = i; + + var o = new SourceObserver(this, j); + _subscriptions[j] = o; + + o.Disposable = srcs[j].Subscribe(o); + } + + SetUpstream(new CompositeDisposable(_subscriptions)); + } + + private void OnNext(int index, TSource value) + { + lock (_gate) + { + _values[index] = value; + + _hasValue[index] = true; + + if (_hasValueAll || (_hasValueAll = _hasValue.All(v => v))) + { + TResult res; + try + { + res = _resultSelector(_values); + } + catch (Exception ex) + { + ForwardOnError(ex); + return; + } + + ForwardOnNext(res); + } + else if (_isDone.Where((_, i) => i != index).All(d => d)) + { + ForwardOnCompleted(); + } + } + } + + private new void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + private void OnCompleted(int index) + { + lock (_gate) + { + _isDone[index] = true; + + if (_isDone.All(d => d)) + { + ForwardOnCompleted(); + } + else + { + _subscriptions[index].Dispose(); + } + } + } + + private sealed class SourceObserver : IObserver, IDisposable + { + private readonly _ _parent; + private readonly int _index; + + public SourceObserver(_ parent, int index) + { + _parent = parent; + _index = index; + } + + public IDisposable? Disposable { get; set; } + + public void OnNext(TSource value) + { + _parent.OnNext(_index, value); + } + + public void OnError(Exception error) + { + _parent.OnError(error); + } + + public void OnCompleted() + { + _parent.OnCompleted(_index); + } + + public void Dispose() + { + Disposable?.Dispose(); + } + } + } +} diff --git a/src/Avalonia.Base/Reactive/Operators/Sink.cs b/src/Avalonia.Base/Reactive/Operators/Sink.cs new file mode 100644 index 0000000000..7a5d39788e --- /dev/null +++ b/src/Avalonia.Base/Reactive/Operators/Sink.cs @@ -0,0 +1,109 @@ +using System; +using System.Threading; + +namespace Avalonia.Reactive.Operators; + +internal abstract class Sink : IDisposable +{ + private IDisposable? _upstream; + private volatile IObserver _observer; + + protected Sink(IObserver observer) + { + _observer = observer; + } + + public void Dispose() + { + Dispose(true); + } + + /// + /// Override this method to dispose additional resources. + /// The method is guaranteed to be called at most once. + /// + /// If true, the method was called from . + protected virtual void Dispose(bool disposing) + { + //Calling base.Dispose(true) is not a proper disposal, so we can omit the assignment here. + //Sink is internal so this can pretty much be enforced. + //_observer = NopObserver.Instance; + + _upstream?.Dispose(); + } + + public void ForwardOnNext(TTarget value) + { + _observer.OnNext(value); + } + + public void ForwardOnCompleted() + { + _observer.OnCompleted(); + Dispose(); + } + + public void ForwardOnError(Exception error) + { + _observer.OnError(error); + Dispose(); + } + + protected void SetUpstream(IDisposable upstream) + { + _upstream = upstream; + } + + protected void DisposeUpstream() + { + _upstream?.Dispose(); + } +} + +internal abstract class Sink : Sink, IObserver +{ + protected Sink(IObserver observer) : base(observer) + { + } + + public virtual void Run(IObservable source) + { + SetUpstream(source.Subscribe(this)); + } + + public abstract void OnNext(TSource value); + + public virtual void OnError(Exception error) => ForwardOnError(error); + + public virtual void OnCompleted() => ForwardOnCompleted(); + + public IObserver GetForwarder() => new _(this); + + private sealed class _ : IObserver + { + private readonly Sink _forward; + + public _(Sink forward) + { + _forward = forward; + } + + public void OnNext(TTarget value) => _forward.ForwardOnNext(value); + + public void OnError(Exception error) => _forward.ForwardOnError(error); + + public void OnCompleted() => _forward.ForwardOnCompleted(); + } +} + +internal abstract class IdentitySink : Sink +{ + protected IdentitySink(IObserver observer) : base(observer) + { + } + + public override void OnNext(T value) + { + ForwardOnNext(value); + } +} diff --git a/src/Avalonia.Base/Reactive/Operators/Switch.cs b/src/Avalonia.Base/Reactive/Operators/Switch.cs new file mode 100644 index 0000000000..bc849c499c --- /dev/null +++ b/src/Avalonia.Base/Reactive/Operators/Switch.cs @@ -0,0 +1,144 @@ +using System; + +namespace Avalonia.Reactive.Operators; + +// Code based on https://github.com/dotnet/reactive/blob/main/Rx.NET/Source/src/System.Reactive/Linq/Observable/Switch.cs + +internal sealed class Switch : IObservable +{ + private readonly IObservable> _sources; + + public Switch(IObservable> sources) + { + _sources = sources; + } + + public IDisposable Subscribe(IObserver observer) + { + return _sources.Subscribe(new _(observer)); + } + + internal sealed class _ : Sink, TSource> + { + private readonly object _gate = new object(); + + public _(IObserver observer) + : base(observer) + { + } + + private IDisposable? _innerSerialDisposable; + private bool _isStopped; + private ulong _latest; + private bool _hasLatest; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _innerSerialDisposable?.Dispose(); + } + + base.Dispose(disposing); + } + + public override void OnNext(IObservable value) + { + ulong id; + + lock (_gate) + { + id = unchecked(++_latest); + _hasLatest = true; + } + + var innerObserver = new InnerObserver(this, id); + + _innerSerialDisposable = innerObserver; + innerObserver.Disposable = value.Subscribe(innerObserver); + } + + public override void OnError(Exception error) + { + lock (_gate) + { + ForwardOnError(error); + } + } + + public override void OnCompleted() + { + lock (_gate) + { + DisposeUpstream(); + + _isStopped = true; + if (!_hasLatest) + { + ForwardOnCompleted(); + } + } + } + + private sealed class InnerObserver : IObserver, IDisposable + { + private readonly _ _parent; + private readonly ulong _id; + + public InnerObserver(_ parent, ulong id) + { + _parent = parent; + _id = id; + } + + public IDisposable? Disposable { get; set; } + + public void OnNext(TSource value) + { + lock (_parent._gate) + { + if (_parent._latest == _id) + { + _parent.ForwardOnNext(value); + } + } + } + + public void OnError(Exception error) + { + lock (_parent._gate) + { + Dispose(); + + if (_parent._latest == _id) + { + _parent.ForwardOnError(error); + } + } + } + + public void OnCompleted() + { + lock (_parent._gate) + { + Dispose(); + + if (_parent._latest == _id) + { + _parent._hasLatest = false; + + if (_parent._isStopped) + { + _parent.ForwardOnCompleted(); + } + } + } + } + + public void Dispose() + { + Disposable?.Dispose(); + } + } + } +} diff --git a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs index 198b36564a..261a39fa09 100644 --- a/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs +++ b/src/Avalonia.Base/Rendering/PlatformRenderInterfaceContextManager.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using Avalonia.Metadata; using Avalonia.Platform; +using Avalonia.Reactive; namespace Avalonia.Rendering; diff --git a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs index a991f2f657..6e05bf8631 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Utilities; diff --git a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs index dd6cf7ad15..1bbf804b5f 100644 --- a/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs +++ b/src/Avalonia.Base/Rendering/UiThreadRenderTimer.cs @@ -1,6 +1,6 @@ using System; using System.Diagnostics; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Threading; namespace Avalonia.Rendering diff --git a/src/Avalonia.Base/Styling/StyleInstance.cs b/src/Avalonia.Base/Styling/StyleInstance.cs index ca602167c0..61cb31c6d0 100644 --- a/src/Avalonia.Base/Styling/StyleInstance.cs +++ b/src/Avalonia.Base/Styling/StyleInstance.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using System.Reactive.Subjects; using Avalonia.Animation; using Avalonia.Data; using Avalonia.PropertyStore; +using Avalonia.Reactive; using Avalonia.Styling.Activators; namespace Avalonia.Styling @@ -24,7 +24,7 @@ namespace Avalonia.Styling private bool _isActive; private List? _setters; private List? _animations; - private Subject? _animationTrigger; + private LightweightSubject? _animationTrigger; public StyleInstance( IStyle style, @@ -67,7 +67,7 @@ namespace Avalonia.Styling { if (_animations is not null && control is Animatable animatable) { - _animationTrigger ??= new Subject(); + _animationTrigger ??= new LightweightSubject(); foreach (var animation in _animations) animation.Apply(animatable, null, _animationTrigger); diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 0c25d89722..f83e4e860e 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Platform; namespace Avalonia.Threading diff --git a/src/Avalonia.Base/Utilities/IWeakSubscriber.cs b/src/Avalonia.Base/Utilities/IWeakSubscriber.cs deleted file mode 100644 index 2a5b8d39c5..0000000000 --- a/src/Avalonia.Base/Utilities/IWeakSubscriber.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace Avalonia.Utilities -{ - /// - /// Defines a listener to a event subscribed vis the . - /// - /// The type of the event arguments. - public interface IWeakSubscriber where T : EventArgs - { - /// - /// Invoked when the subscribed event is raised. - /// - /// The event sender. - /// The event arguments. - void OnEvent(object? sender, T e); - } -} diff --git a/src/Avalonia.Base/Utilities/WeakObservable.cs b/src/Avalonia.Base/Utilities/WeakObservable.cs deleted file mode 100644 index e1c350d539..0000000000 --- a/src/Avalonia.Base/Utilities/WeakObservable.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Reactive; -using System.Reactive.Linq; - -namespace Avalonia.Utilities -{ - /// - /// Provides extension methods for working with weak event handlers. - /// - public static class WeakObservable - { - - private class Handler - : IWeakSubscriber, - IWeakEventSubscriber where TEventArgs : EventArgs - { - private IObserver> _observer; - - public Handler(IObserver> observer) - { - _observer = observer; - } - - public void OnEvent(object? sender, TEventArgs e) - { - _observer.OnNext(new EventPattern(sender, e)); - } - - public void OnEvent(object? sender, WeakEvent ev, TEventArgs e) - { - _observer.OnNext(new EventPattern(sender, e)); - } - } - - /// - /// Converts a WeakEvent conforming to the standard .NET event pattern into an observable - /// sequence, subscribing weakly. - /// - /// The type of target. - /// The type of the event args. - /// Object instance that exposes the event to convert. - /// The weak event to convert. - /// - public static IObservable> FromEventPattern( - TTarget target, WeakEvent ev) - where TEventArgs : EventArgs where TTarget : class - { - _ = target ?? throw new ArgumentNullException(nameof(target)); - _ = ev ?? throw new ArgumentNullException(nameof(ev)); - - return Observable.Create>(observer => - { - var handler = new Handler(observer); - ev.Subscribe(target, handler); - return () => ev.Unsubscribe(target, handler); - }).Publish().RefCount(); - } - - } -} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index ddaea01b05..5ff887e339 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -11,6 +11,7 @@ using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Server; @@ -384,52 +385,55 @@ namespace Avalonia protected static void AffectsRender(params AvaloniaProperty[] properties) where T : Visual { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is T sender) + var invalidateObserver = new AnonymousObserver( + static e => { - sender.InvalidateVisual(); - } - } - - static void InvalidateAndSubscribe(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is T sender) + if (e.Sender is T sender) + { + sender.InvalidateVisual(); + } + }); + + + var invalidateAndSubscribeObserver = new AnonymousObserver( + static e => { - if (e.OldValue is IAffectsRender oldValue) + if (e.Sender is T sender) { - if (sender._affectsRenderWeakSubscriber != null) + if (e.OldValue is IAffectsRender oldValue) { - InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber); + if (sender._affectsRenderWeakSubscriber != null) + { + InvalidatedWeakEvent.Unsubscribe(oldValue, sender._affectsRenderWeakSubscriber); + } } - } - if (e.NewValue is IAffectsRender newValue) - { - if (sender._affectsRenderWeakSubscriber == null) + if (e.NewValue is IAffectsRender newValue) { - sender._affectsRenderWeakSubscriber = new TargetWeakEventSubscriber( - sender, static (target, _, _, _) => - { - target.InvalidateVisual(); - }); + if (sender._affectsRenderWeakSubscriber == null) + { + sender._affectsRenderWeakSubscriber = new TargetWeakEventSubscriber( + sender, static (target, _, _, _) => + { + target.InvalidateVisual(); + }); + } + InvalidatedWeakEvent.Subscribe(newValue, sender._affectsRenderWeakSubscriber); } - InvalidatedWeakEvent.Subscribe(newValue, sender._affectsRenderWeakSubscriber); - } - sender.InvalidateVisual(); - } - } + sender.InvalidateVisual(); + } + }); foreach (var property in properties) { if (property.CanValueAffectRender()) { - property.Changed.Subscribe(e => InvalidateAndSubscribe(e)); + property.Changed.Subscribe(invalidateAndSubscribeObserver); } else { - property.Changed.Subscribe(e => Invalidate(e)); + property.Changed.Subscribe(invalidateObserver); } } } @@ -616,23 +620,22 @@ namespace Avalonia /// Called when a visual's changes. /// /// The event args. - private static void RenderTransformChanged(AvaloniaPropertyChangedEventArgs e) + private static void RenderTransformChanged(AvaloniaPropertyChangedEventArgs e) { var sender = e.Sender as Visual; if (sender?.VisualRoot != null) { - var oldValue = e.OldValue as Transform; - var newValue = e.NewValue as Transform; + var (oldValue, newValue) = e.GetOldAndNewValue(); - if (oldValue != null) + if (oldValue is Transform oldTransform) { - oldValue.Changed -= sender.RenderTransformChanged; + oldTransform.Changed -= sender.RenderTransformChanged; } - if (newValue != null) + if (newValue is Transform newTransform) { - newValue.Changed += sender.RenderTransformChanged; + newTransform.Changed += sender.RenderTransformChanged; } sender.InvalidateVisual(); diff --git a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj index f3f8e4c82c..c79468b48d 100644 --- a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj +++ b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 2df46889a9..6f4c0003a8 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -15,6 +15,7 @@ using Avalonia.Media; using Avalonia.Media.Imaging; using Avalonia.Threading; using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Controls.Primitives { diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index 6369961f0f..c3135cefce 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 5dcad83601..b7546a4625 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -27,6 +27,7 @@ using Avalonia.Layout; using Avalonia.Controls.Metadata; using Avalonia.Input.GestureRecognizers; using Avalonia.Styling; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index ce74604f70..e859a6e725 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -4,12 +4,7 @@ // All other rights reserved. using Avalonia.Data; -using Avalonia.Utilities; using System; -using System.Reactive.Disposables; -using System.Reactive.Subjects; -using Avalonia.Reactive; -using System.Diagnostics; using Avalonia.Controls.Utils; using Avalonia.Markup.Xaml.MarkupExtensions; diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index dfa6ea2e46..c4010487af 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -15,6 +15,7 @@ using Avalonia.Utilities; using Avalonia.VisualTree; using System; using System.Diagnostics; +using Avalonia.Reactive; namespace Avalonia.Controls { @@ -1021,11 +1022,11 @@ namespace Avalonia.Controls { layoutableContent.LayoutUpdated += DetailsContent_LayoutUpdated; - _detailsContentSizeSubscription = - System.Reactive.Disposables.StableCompositeDisposable.Create( - System.Reactive.Disposables.Disposable.Create(() => layoutableContent.LayoutUpdated -= DetailsContent_LayoutUpdated), - _detailsContent.GetObservable(MarginProperty) - .Subscribe(DetailsContent_MarginChanged)); + _detailsContentSizeSubscription = new CompositeDisposable(2) + { + Disposable.Create(() => layoutableContent.LayoutUpdated -= DetailsContent_LayoutUpdated), + _detailsContent.GetObservable(MarginProperty).Subscribe(DetailsContent_MarginChanged) + }; } diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index c746b19cc7..10efded58a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -10,7 +10,7 @@ using Avalonia.Input; using Avalonia.Media; using System; using System.Diagnostics; -using System.Reactive.Linq; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs index 1d1a595ccf..d6c46bb1e0 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs @@ -2,7 +2,7 @@ using Avalonia.Reactive; using System; using System.Collections.Generic; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Controls.Utils { @@ -16,16 +16,16 @@ namespace Avalonia.Controls.Utils internal class CellEditBinding : ICellEditBinding { - private readonly Subject _changedSubject = new Subject(); + private readonly LightweightSubject _changedSubject = new(); private readonly List _validationErrors = new List(); private readonly SubjectWrapper _inner; public bool IsValid => _validationErrors.Count <= 0; public IEnumerable ValidationErrors => _validationErrors; public IObservable ValidationChanged => _changedSubject; - public ISubject InternalSubject => _inner; + public IAvaloniaSubject InternalSubject => _inner; - public CellEditBinding(ISubject bindingSourceSubject) + public CellEditBinding(IAvaloniaSubject bindingSourceSubject) { _inner = new SubjectWrapper(bindingSourceSubject, this); } @@ -48,16 +48,16 @@ namespace Avalonia.Controls.Utils return IsValid; } - class SubjectWrapper : LightweightObservableBase, ISubject, IDisposable + class SubjectWrapper : LightweightObservableBase, IAvaloniaSubject, IDisposable { - private readonly ISubject _sourceSubject; + private readonly IAvaloniaSubject _sourceSubject; private readonly CellEditBinding _editBinding; private IDisposable _subscription; private object _controlValue; private bool _isControlValueSet = false; private bool _settingSourceValue = false; - public SubjectWrapper(ISubject bindingSourceSubject, CellEditBinding editBinding) + public SubjectWrapper(IAvaloniaSubject bindingSourceSubject, CellEditBinding editBinding) { _sourceSubject = bindingSourceSubject; _editBinding = editBinding; diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index dc9a0207ad..5b652cce19 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive.Concurrency; -using System.Threading; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; @@ -231,7 +229,6 @@ namespace Avalonia .Bind().ToConstant(FocusManager) .Bind().ToConstant(InputManager) .Bind().ToTransient() - .Bind().ToConstant(AvaloniaScheduler.Instance) .Bind().ToConstant(DragDropDevice.Instance); // TODO: Fix this, for now we keep this behavior since someone might be relying on it in 0.9.x diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index e0d986f2b4..c84e96ed6d 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -10,7 +10,7 @@ using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading; using System.Threading.Tasks; using Avalonia.Collections; diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 2710ac5cc2..20ecb15079 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -6,7 +6,6 @@ - diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 8e5d4e1e06..1ec6f8dabc 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -5,6 +5,7 @@ using System.Windows.Input; using Avalonia.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs index e455c6c6f3..4b2997f431 100644 --- a/src/Avalonia.Controls/ButtonSpinner.cs +++ b/src/Avalonia.Controls/ButtonSpinner.cs @@ -1,7 +1,6 @@ -using System; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; -using Avalonia.Data; +using Avalonia.Reactive; using Avalonia.Input; using Avalonia.Interactivity; diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index ec1273ca98..b17648f5bb 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -7,7 +7,7 @@ using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; diff --git a/src/Avalonia.Controls/Canvas.cs b/src/Avalonia.Controls/Canvas.cs index adee7d4d90..cd81c6e59b 100644 --- a/src/Avalonia.Controls/Canvas.cs +++ b/src/Avalonia.Controls/Canvas.cs @@ -1,7 +1,4 @@ -using System; -using System.Reactive.Concurrency; using Avalonia.Input; -using Avalonia.Layout; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs index 6d7e542bb2..f5fcbed9fb 100644 --- a/src/Avalonia.Controls/Chrome/CaptionButtons.cs +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Chrome [PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] public class CaptionButtons : TemplatedControl { - private CompositeDisposable? _disposables; + private IDisposable? _disposables; /// /// Currently attached window. @@ -28,17 +28,14 @@ namespace Avalonia.Controls.Chrome { HostWindow = hostWindow; - _disposables = new CompositeDisposable - { - HostWindow.GetObservable(Window.WindowStateProperty) + _disposables = HostWindow.GetObservable(Window.WindowStateProperty) .Subscribe(x => { PseudoClasses.Set(":minimized", x == WindowState.Minimized); PseudoClasses.Set(":normal", x == WindowState.Normal); PseudoClasses.Set(":maximized", x == WindowState.Maximized); PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); - }) - }; + }); } } diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 1bf13111a9..47b0bb6e2d 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; @@ -61,7 +61,7 @@ namespace Avalonia.Controls.Chrome if (VisualRoot is Window window) { - _disposables = new CompositeDisposable + _disposables = new CompositeDisposable(6) { window.GetObservable(Window.WindowDecorationMarginProperty) .Subscribe(x => UpdateSize(window)), diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index a0085c92ca..839a3e3ba0 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using Avalonia.Automation.Peers; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Generators; using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; diff --git a/src/Avalonia.Controls/ComboBoxItem.cs b/src/Avalonia.Controls/ComboBoxItem.cs index 83057d139f..7db713d692 100644 --- a/src/Avalonia.Controls/ComboBoxItem.cs +++ b/src/Avalonia.Controls/ComboBoxItem.cs @@ -1,5 +1,4 @@ -using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Automation; using Avalonia.Automation.Peers; @@ -12,8 +11,14 @@ namespace Avalonia.Controls { public ComboBoxItem() { - this.GetObservable(ComboBoxItem.IsFocusedProperty).Where(focused => focused) - .Subscribe(_ => (Parent as ComboBox)?.ItemFocused(this)); + this.GetObservable(ComboBoxItem.IsFocusedProperty) + .Subscribe(focused => + { + if (focused) + { + (Parent as ComboBox)?.ItemFocused(this); + } + }); } static ComboBoxItem() diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 020a6de539..b7ff75339d 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -15,6 +15,7 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Styling; using Avalonia.Automation; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index bceeb58adb..889a4cc79f 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -1,8 +1,5 @@ using System; -using System.Linq; -using Avalonia.Data; -using Avalonia.LogicalTree; -using Avalonia.Styling; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/DataValidationErrors.cs b/src/Avalonia.Controls/DataValidationErrors.cs index 00a06101c5..b082909807 100644 --- a/src/Avalonia.Controls/DataValidationErrors.cs +++ b/src/Avalonia.Controls/DataValidationErrors.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Templates; using Avalonia.Data; diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 64a02ccb46..f93f613583 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; +using Avalonia.Reactive; using Avalonia.Utilities; namespace Avalonia.Controls diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index 30bdb4c60e..e4487d29fa 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -3,6 +3,7 @@ using Avalonia.Layout; using Avalonia.Media; using Avalonia.Platform; using System; +using Avalonia.Reactive; using Avalonia.Media.Immutable; namespace Avalonia.Controls diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs index 8455495830..9d4abec549 100644 --- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs @@ -7,6 +7,7 @@ using Avalonia.Input.Platform; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; +using Avalonia.Reactive; namespace Avalonia.Controls.Primitives { diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs index dc3c621db4..de753f0bd0 100644 --- a/src/Avalonia.Controls/HotkeyManager.cs +++ b/src/Avalonia.Controls/HotkeyManager.cs @@ -2,6 +2,7 @@ using System; using System.Windows.Input; using Avalonia.Controls.Utils; using Avalonia.Input; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 766a712c88..ce254684b7 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -4,7 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Media; namespace Avalonia.Controls @@ -424,9 +424,9 @@ namespace Avalonia.Controls if (newTransform != null) { - _transformChangedEvent = Observable.FromEventPattern( + _transformChangedEvent = Observable.FromEventPattern( v => newTransform.Changed += v, v => newTransform.Changed -= v) - .Subscribe(onNext: v => ApplyLayoutTransform()); + .Subscribe(_ => ApplyLayoutTransform()); } ApplyLayoutTransform(); diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 3e00cea430..40941d00be 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Windows.Input; using Avalonia.Automation.Peers; using Avalonia.Controls.Generators; @@ -159,12 +159,13 @@ namespace Avalonia.Controls // menu layout. var parentSharedSizeScope = this.GetObservable(VisualParentProperty) - .SelectMany(x => + .Select(x => { var parent = x as Control; return parent?.GetObservable(DefinitionBase.PrivateSharedSizeScopeProperty) ?? Observable.Return(null); - }); + }) + .Switch(); this.Bind(DefinitionBase.PrivateSharedSizeScopeProperty, parentSharedSizeScope); } diff --git a/src/Avalonia.Controls/Mixins/DisposableMixin.cs b/src/Avalonia.Controls/Mixins/DisposableMixin.cs deleted file mode 100644 index 9b30b4ba4c..0000000000 --- a/src/Avalonia.Controls/Mixins/DisposableMixin.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Reactive.Disposables; - -namespace Avalonia.Controls.Mixins -{ - /// - /// Extension methods associated with the IDisposable interface. - /// - public static class DisposableMixin - { - /// - /// Ensures the provided disposable is disposed with the specified . - /// - /// - /// The type of the disposable. - /// - /// - /// The disposable we are going to want to be disposed by the CompositeDisposable. - /// - /// - /// The to which will be added. - /// - /// - /// The disposable. - /// - public static T DisposeWith(this T item, CompositeDisposable compositeDisposable) - where T : IDisposable - { - if (compositeDisposable is null) - { - throw new ArgumentNullException(nameof(compositeDisposable)); - } - - compositeDisposable.Add(item); - return item; - } - } -} diff --git a/src/Avalonia.Controls/Mixins/SelectableMixin.cs b/src/Avalonia.Controls/Mixins/SelectableMixin.cs index a04a741e3e..2a8fe7b976 100644 --- a/src/Avalonia.Controls/Mixins/SelectableMixin.cs +++ b/src/Avalonia.Controls/Mixins/SelectableMixin.cs @@ -1,7 +1,7 @@ using System; using Avalonia.Interactivity; using Avalonia.Controls.Primitives; -using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Controls.Mixins { diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index fe650ab41e..c556ce7b02 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -1,7 +1,6 @@ using System; -using System.Collections.Generic; using Avalonia.Controls.Platform; -using Avalonia.Data; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs index ac7a45a547..3953de8165 100644 --- a/src/Avalonia.Controls/NativeMenuBar.cs +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics.CodeAnalysis; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index d22fdb2f84..526eff0f12 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -4,6 +4,7 @@ using Avalonia.Input; using Avalonia.Media.Imaging; using Avalonia.Metadata; using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/Notifications/NotificationCard.cs b/src/Avalonia.Controls/Notifications/NotificationCard.cs index a103adf185..663bd3358a 100644 --- a/src/Avalonia.Controls/Notifications/NotificationCard.cs +++ b/src/Avalonia.Controls/Notifications/NotificationCard.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Interactivity; using Avalonia.LogicalTree; @@ -37,30 +37,29 @@ namespace Avalonia.Controls.Notifications RaiseEvent(new RoutedEventArgs(NotificationClosedEvent)); }); - // Disabling nullable checking because of https://github.com/dotnet/reactive/issues/1525 -#pragma warning disable CS8620 this.GetObservable(ContentProperty) - .OfType() -#pragma warning restore CS8620 .Subscribe(x => { - switch (x.Type) + if (x is Notification notification) { - case NotificationType.Error: - PseudoClasses.Add(":error"); - break; - - case NotificationType.Information: - PseudoClasses.Add(":information"); - break; - - case NotificationType.Success: - PseudoClasses.Add(":success"); - break; - - case NotificationType.Warning: - PseudoClasses.Add(":warning"); - break; + switch (notification.Type) + { + case NotificationType.Error: + PseudoClasses.Add(":error"); + break; + + case NotificationType.Information: + PseudoClasses.Add(":information"); + break; + + case NotificationType.Success: + PseudoClasses.Add(":success"); + break; + + case NotificationType.Warning: + PseudoClasses.Add(":warning"); + break; + } } }); } diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs index 46c772f3b1..3ccddf4155 100644 --- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs +++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index 8ed6abd52b..ac4f699313 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -9,6 +9,7 @@ using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.Reactive; using Avalonia.Threading; using Avalonia.Utilities; diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index 7134e1b9e7..a643edc420 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -5,6 +5,7 @@ using System.Linq; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Metadata; +using Avalonia.Reactive; using Avalonia.Styling; namespace Avalonia.Controls diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 6d525da150..e09da02f17 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -6,9 +6,9 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.Platform; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Controls.Platform { diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 5b2356a7ce..1a262aa493 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -1,7 +1,5 @@ -using System; -using System.Linq; -using System.Reactive.Linq; -using System.Reactive.Subjects; +using System.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Input; @@ -17,7 +15,7 @@ namespace Avalonia.Platform private const RawInputModifiers MOUSE_INPUTMODIFIERS = RawInputModifiers.LeftMouseButton|RawInputModifiers.MiddleMouseButton|RawInputModifiers.RightMouseButton; private readonly IDragDropDevice _dragDrop; private readonly IInputManager _inputManager; - private readonly Subject _result = new Subject(); + private readonly LightweightSubject _result = new(); private DragDropEffects _allowedEffects; private IDataObject? _draggedData; @@ -44,11 +42,25 @@ namespace Avalonia.Platform _lastPosition = default(Point); _allowedEffects = allowedEffects; - using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) + var inputObserver = new AnonymousObserver(arg => { - using (_inputManager.PreProcess.OfType().Subscribe(ProcessKeyEvents)) + switch (arg) { - var effect = await _result.FirstAsync(); + case RawPointerEventArgs pointerEventArgs: + ProcessMouseEvents(pointerEventArgs); + break; + case RawKeyEventArgs keyEventArgs: + ProcessKeyEvents(keyEventArgs); + break; + } + }); + + using (_inputManager.PreProcess.Subscribe(inputObserver)) + { + var tcs = new TaskCompletionSource(); + using (_result.Subscribe(new AnonymousObserver(tcs))) + { + var effect = await tcs.Task; return effect; } } diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index de7708e869..d678911e1f 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Metadata; using Avalonia.Platform; diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index 7b435e287d..0e283c3c4b 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -1,6 +1,6 @@ using System.Collections.Specialized; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Animation; using Avalonia.Controls.Primitives; diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs index 1f0f2a2f67..b9f856cf39 100644 --- a/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs +++ b/src/Avalonia.Controls/Presenters/ItemVirtualizer.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using System.Collections.Specialized; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Primitives; using Avalonia.Controls.Utils; using Avalonia.Input; diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 328facba0b..254515755d 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Utilities; diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index 7e5b34acd9..49e76d0728 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -1,7 +1,6 @@ -using System; -using System.Diagnostics.CodeAnalysis; using Avalonia.Automation.Peers; using Avalonia.Input; +using Avalonia.Reactive; using Avalonia.Media; using Avalonia.Media.TextFormatting; diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 89abe1cdaa..c9585d50ae 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Specialized; using Avalonia.Media; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.VisualTree; diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index cd26ea4f6e..e0f72cae54 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Primitives.PopupPositioning; using Avalonia.Interactivity; using Avalonia.Media; diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 3c329a9a3e..bf79198939 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -1,6 +1,6 @@ using System; using System.ComponentModel; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Automation.Peers; using Avalonia.Controls.Mixins; using Avalonia.Controls.Diagnostics; diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 165bec3a95..7e0d695264 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Layout; using Avalonia.Media; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs index d0b8178add..2c29b19d48 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshContainer.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.PullToRefresh; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs index f2f735aaa9..8723304f8f 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs @@ -1,12 +1,12 @@ using System; using System.Numerics; -using System.Reactive.Linq; using Avalonia.Animation.Easings; using Avalonia.Controls.Primitives; using Avalonia.Controls.PullToRefresh; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; +using Avalonia.Reactive; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Animations; diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs index 9dbc5f040e..68da24d79f 100644 --- a/src/Avalonia.Controls/RadioButton.cs +++ b/src/Avalonia.Controls/RadioButton.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Avalonia.Controls.Primitives; +using Avalonia.Reactive; using Avalonia.Rendering; using Avalonia.VisualTree; diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index 10e3dd57a0..d7f24ec537 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -4,16 +4,8 @@ // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; -using System.Collections.Generic; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Threading.Tasks; -using Avalonia.Controls.Presenters; -using Avalonia.Input; using Avalonia.Layout; using Avalonia.Logging; -using Avalonia.Media; -using Avalonia.Reactive; using Avalonia.Threading; using Avalonia.VisualTree; diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 12fb9ba5c5..503187e2d3 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Automation.Peers; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; diff --git a/src/Avalonia.Controls/Shapes/Shape.cs b/src/Avalonia.Controls/Shapes/Shape.cs index e2a13512a5..c8bf95b3f7 100644 --- a/src/Avalonia.Controls/Shapes/Shape.cs +++ b/src/Avalonia.Controls/Shapes/Shape.cs @@ -2,6 +2,7 @@ using System; using Avalonia.Collections; using Avalonia.Media; using Avalonia.Media.Immutable; +using Avalonia.Reactive; namespace Avalonia.Controls.Shapes { diff --git a/src/Avalonia.Controls/Spinner.cs b/src/Avalonia.Controls/Spinner.cs index cfcf0ee376..bf6ed060bd 100644 --- a/src/Avalonia.Controls/Spinner.cs +++ b/src/Avalonia.Controls/Spinner.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/SplitButton/SplitButton.cs b/src/Avalonia.Controls/SplitButton/SplitButton.cs index f39064435d..1d2bab8d27 100644 --- a/src/Avalonia.Controls/SplitButton/SplitButton.cs +++ b/src/Avalonia.Controls/SplitButton/SplitButton.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/SplitView.cs b/src/Avalonia.Controls/SplitView.cs index fcadafaab5..35b135e152 100644 --- a/src/Avalonia.Controls/SplitView.cs +++ b/src/Avalonia.Controls/SplitView.cs @@ -6,6 +6,7 @@ using Avalonia.Media; using Avalonia.Metadata; using Avalonia.VisualTree; using System; +using Avalonia.Reactive; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.LogicalTree; diff --git a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs index cfd4234a27..6fedf2b1cd 100644 --- a/src/Avalonia.Controls/Templates/FuncDataTemplate.cs +++ b/src/Avalonia.Controls/Templates/FuncDataTemplate.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Primitives; namespace Avalonia.Controls.Templates diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1bdec878d9..6938027510 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -2,7 +2,7 @@ using Avalonia.Input.Platform; using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Utils; diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs index bb18bf4c64..d3339e3edf 100644 --- a/src/Avalonia.Controls/ToolTip.cs +++ b/src/Avalonia.Controls/ToolTip.cs @@ -2,6 +2,7 @@ using System; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 2cd55dc3ab..1f5b8f80d2 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Notifications; using Avalonia.Controls.Platform; diff --git a/src/Avalonia.Controls/TrayIcon.cs b/src/Avalonia.Controls/TrayIcon.cs index 41a1abd838..d1a7a1f727 100644 --- a/src/Avalonia.Controls/TrayIcon.cs +++ b/src/Avalonia.Controls/TrayIcon.cs @@ -6,7 +6,7 @@ using Avalonia.Collections; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Platform; using Avalonia.Platform; -using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index ffdd32f95c..f98bab7d40 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Collections; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs index d2e3feb55a..f8e06ba35d 100644 --- a/src/Avalonia.Controls/Utils/AncestorFinder.cs +++ b/src/Avalonia.Controls/Utils/AncestorFinder.cs @@ -1,8 +1,5 @@ using System; -using System.Reactive; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using System.Reactive.Subjects; +using Avalonia.Reactive; namespace Avalonia.Controls.Utils { @@ -13,7 +10,7 @@ namespace Avalonia.Controls.Utils private readonly StyledElement _control; private readonly Type _ancestorType; public IObservable Observable => _subject; - private readonly Subject _subject = new Subject(); + private readonly LightweightSubject _subject = new(); private FinderNode? _child; private IDisposable? _disposable; @@ -31,24 +28,21 @@ namespace Avalonia.Controls.Utils private void OnValueChanged(StyledElement? next) { - if (next == null || _ancestorType.IsAssignableFrom(next.GetType())) + if (next == null || _ancestorType.IsInstanceOfType(next)) _subject.OnNext(next); else { _child?.Dispose(); _child = new FinderNode(next, _ancestorType); - _child.Observable.Subscribe(OnChildValueChanged); + _child.Observable.Subscribe(_subject); _child.Init(); } } - private void OnChildValueChanged(StyledElement? control) => _subject.OnNext(control); - - public void Dispose() { _child?.Dispose(); - _subject.Dispose(); + _subject.OnCompleted(); _disposable?.Dispose(); } } @@ -61,7 +55,7 @@ namespace Avalonia.Controls.Utils public static IObservable Create(StyledElement control, Type ancestorType) { - return new AnonymousObservable(observer => + return Observable.Create(observer => { var finder = new FinderNode(control, ancestorType); var subscription = finder.Observable.Subscribe(observer); diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 88f3b520ce..a20b4eee58 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Automation.Peers; using Avalonia.Controls.Platform; @@ -800,7 +800,7 @@ namespace Avalonia.Controls Renderer?.Start(); - Observable.FromEventPattern( + Observable.FromEventPattern( x => Closed += x, x => Closed -= x) .Take(1) diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index b71dc6df44..aad0482b50 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -1,14 +1,9 @@ using System; using System.ComponentModel; -using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; -using Avalonia.Automation.Peers; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; -using JetBrains.Annotations; namespace Avalonia.Controls { diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 867c94b126..c5255b22cd 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -19,6 +19,5 @@ - diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 94679e8ade..3238b394fc 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Automation.Peers; using Avalonia.Controls; diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 8eadcee6f9..9b4e13959b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Views; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Interactivity; +using Avalonia.Reactive; namespace Avalonia.Diagnostics { @@ -90,7 +90,7 @@ namespace Avalonia.Diagnostics { s_attachedToApplication = true; - application.InputManager.PreProcess.OfType().Subscribe(e => + ObservableExtensions.Subscribe(application.InputManager.PreProcess.OfType(), e => { if (options.Gesture.Matches(e)) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index e71f0bcaec..7c6246701a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -84,7 +84,7 @@ namespace Avalonia.Diagnostics.ViewModels } nodes.Add(new LogicalTreeNode(window, Owner)); } - _subscriptions = new System.Reactive.Disposables.CompositeDisposable() + _subscriptions = new CompositeDisposable(2) { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=> { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index a1cd01c78b..e44b852372 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -188,7 +188,7 @@ namespace Avalonia.Diagnostics.ViewModels } nodes.Add(new VisualTreeNode(window, Owner)); } - _subscriptions = new System.Reactive.Disposables.CompositeDisposable() + _subscriptions = new CompositeDisposable(2) { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=> { diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs index 7d0072c6ea..a2f8af0575 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Specialized; using System.IO; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Runtime.InteropServices; using Avalonia.Collections; using Avalonia.Controls; @@ -120,10 +119,8 @@ namespace Avalonia.Dialogs.Internal .GetService() .Listen(ManagedFileChooserSources.MountedVolumes); - var sub2 = Observable.FromEventPattern(ManagedFileChooserSources.MountedVolumes, - nameof(ManagedFileChooserSources.MountedVolumes.CollectionChanged)) - .ObserveOn(AvaloniaScheduler.Instance) - .Subscribe(x => RefreshQuickLinks(quickSources)); + var sub2 = ManagedFileChooserSources.MountedVolumes.GetWeakCollectionChangedObservable() + .Subscribe(x => Dispatcher.UIThread.Post(() => RefreshQuickLinks(quickSources))); _disposables.Add(sub1); _disposables.Add(sub2); diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 791431dfa7..0f499c6066 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Reactive.Concurrency; using System.Reflection; using System.Threading.Tasks; using Avalonia.Input; diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 657e324010..560d6fc252 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index e37067d05c..b44762161b 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -2,7 +2,7 @@ using System; using System.Diagnostics; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Avalonia.Controls.Platform; diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs index 34c1506a67..9f6b056d30 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoListener.cs @@ -4,11 +4,12 @@ using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; using Avalonia.Controls.Platform; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; +using Avalonia.Reactive; using System.Text.RegularExpressions; using System.Runtime.InteropServices; using System.Text; +using Avalonia.Threading; namespace Avalonia.FreeDesktop { @@ -17,21 +18,17 @@ namespace Avalonia.FreeDesktop private const string DevByLabelDir = "/dev/disk/by-label/"; private const string ProcPartitionsDir = "/proc/partitions"; private const string ProcMountsDir = "/proc/mounts"; - private CompositeDisposable _disposables; + private IDisposable _disposable; private ObservableCollection _targetObs; private bool _beenDisposed = false; public LinuxMountedVolumeInfoListener(ref ObservableCollection target) { - _disposables = new CompositeDisposable(); this._targetObs = target; - var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) - .Subscribe(Poll); + _disposable = DispatcherTimer.Run(Poll, TimeSpan.FromSeconds(1)); - _disposables.Add(pollTimer); - - Poll(0); + Poll(); } private static string GetSymlinkTarget(string x) => Path.GetFullPath(Path.Combine(DevByLabelDir, NativeMethods.ReadLink(x))); @@ -43,7 +40,7 @@ namespace Avalonia.FreeDesktop private static string UnescapeDeviceLabel(string input) => UnescapeString(input, @"\\x([0-9a-f]{2})", 16); - private void Poll(long _) + private bool Poll() { var fProcPartitions = File.ReadAllLines(ProcPartitionsDir) .Skip(1) @@ -77,13 +74,14 @@ namespace Avalonia.FreeDesktop var mountVolInfos = q1.ToArray(); if (_targetObs.SequenceEqual(mountVolInfos)) - return; + return true; else { _targetObs.Clear(); foreach (var i in mountVolInfos) _targetObs.Add(i); + return true; } } @@ -93,7 +91,7 @@ namespace Avalonia.FreeDesktop { if (disposing) { - _disposables.Dispose(); + _disposable.Dispose(); _targetObs.Clear(); } diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 8da10fa59b..e846309c24 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -1,6 +1,6 @@ using System; using System.Diagnostics; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; diff --git a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs index b233b46dd0..046e4645e3 100644 --- a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Threading; using Avalonia.Platform; using Avalonia.Threading; diff --git a/src/Avalonia.Native/IAvnMenu.cs b/src/Avalonia.Native/IAvnMenu.cs index e6a5a371d0..515835a3bd 100644 --- a/src/Avalonia.Native/IAvnMenu.cs +++ b/src/Avalonia.Native/IAvnMenu.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls; namespace Avalonia.Native.Interop diff --git a/src/Avalonia.Native/IAvnMenuItem.cs b/src/Avalonia.Native/IAvnMenuItem.cs index de3be6142e..424d2c7310 100644 --- a/src/Avalonia.Native/IAvnMenuItem.cs +++ b/src/Avalonia.Native/IAvnMenuItem.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls; using Avalonia.Media.Imaging; using Avalonia.Platform.Interop; diff --git a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs index 92b2915e2e..59e14758ef 100644 --- a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs +++ b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs @@ -2,15 +2,16 @@ using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; +using Avalonia.Reactive; using Avalonia.Controls.Platform; +using Avalonia.Threading; namespace Avalonia.Native { internal class MacOSMountedVolumeInfoListener : IDisposable { - private readonly CompositeDisposable _disposables; + private readonly IDisposable _disposable; private bool _beenDisposed = false; private ObservableCollection mountedDrives; @@ -18,17 +19,12 @@ namespace Avalonia.Native { this.mountedDrives = mountedDrives; - _disposables = new CompositeDisposable(); + _disposable = DispatcherTimer.Run(Poll, TimeSpan.FromSeconds(1)); - var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) - .Subscribe(Poll); - - _disposables.Add(pollTimer); - - Poll(0); + Poll(); } - private void Poll(long _) + private bool Poll() { var mountVolInfos = Directory.GetDirectories("/Volumes/") .Where(p=> p != null) @@ -41,13 +37,14 @@ namespace Avalonia.Native .ToArray(); if (mountedDrives.SequenceEqual(mountVolInfos)) - return; + return true; else { mountedDrives.Clear(); foreach (var i in mountVolInfos) mountedDrives.Add(i); + return true; } } diff --git a/src/Avalonia.OpenGL/Egl/EglContext.cs b/src/Avalonia.OpenGL/Egl/EglContext.cs index 4d75a776c3..0b4b7d26a1 100644 --- a/src/Avalonia.OpenGL/Egl/EglContext.cs +++ b/src/Avalonia.OpenGL/Egl/EglContext.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Threading; using Avalonia.Platform; +using Avalonia.Reactive; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl diff --git a/src/Avalonia.OpenGL/Egl/EglDisplay.cs b/src/Avalonia.OpenGL/Egl/EglDisplay.cs index eea2587587..c67d7674a8 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplay.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplay.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; +using Avalonia.Reactive; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl diff --git a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs index fbfaf1bd3d..5573eb39fa 100644 --- a/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs +++ b/src/Avalonia.OpenGL/Egl/EglDisplayUtils.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; using static Avalonia.OpenGL.Egl.EglConsts; namespace Avalonia.OpenGL.Egl; @@ -132,4 +131,4 @@ class EglConfigInfo SampleCount = sampleCount; StencilSize = stencilSize; } -} \ No newline at end of file +} diff --git a/src/Avalonia.ReactiveUI/Attributes.cs b/src/Avalonia.ReactiveUI/Attributes.cs index 31c374392f..1b85cd2f47 100644 --- a/src/Avalonia.ReactiveUI/Attributes.cs +++ b/src/Avalonia.ReactiveUI/Attributes.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using Avalonia.Metadata; [assembly: XmlnsDefinition("http://reactiveui.net", "Avalonia.ReactiveUI")] \ No newline at end of file diff --git a/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs b/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs index bd21503a58..ae9a8787bc 100644 --- a/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs +++ b/src/Avalonia.ReactiveUI/AutoSuspendHelper.cs @@ -2,10 +2,10 @@ using Avalonia; using Avalonia.VisualTree; using Avalonia.Controls; using System.Threading; +using System.Reactive; using System.Reactive.Disposables; using System.Reactive.Subjects; using System.Reactive.Linq; -using System.Reactive; using ReactiveUI; using System; using Avalonia.Controls.ApplicationLifetimes; diff --git a/src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs b/src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs new file mode 100644 index 0000000000..4cd329beb6 --- /dev/null +++ b/src/Avalonia.ReactiveUI/AvaloniaObjectReactiveExtensions.cs @@ -0,0 +1,110 @@ +using System.Reactive; +using System.Reactive.Subjects; +using Avalonia.Data; + +namespace Avalonia.ReactiveUI; + +public static class AvaloniaObjectReactiveExtensions +{ + /// + /// Gets a subject for an . + /// + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create( + Observer.Create(x => o.SetValue(property, x, priority)), + o.GetObservable(property)); + } + + /// + /// Gets a subject for an . + /// + /// The property type. + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject GetSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create( + Observer.Create(x => o.SetValue(property, x, priority)), + o.GetObservable(property)); + } + + /// + /// Gets a subject for a . + /// + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject> GetBindingSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create>( + Observer.Create>(x => + { + if (x.HasValue) + { + o.SetValue(property, x.Value, priority); + } + }), + o.GetBindingObservable(property)); + } + + /// + /// Gets a subject for a . + /// + /// The property type. + /// The object. + /// The property. + /// + /// The priority with which binding values are written to the object. + /// + /// + /// An which can be used for two-way binding to/from the + /// property. + /// + public static ISubject> GetBindingSubject( + this AvaloniaObject o, + AvaloniaProperty property, + BindingPriority priority = BindingPriority.LocalValue) + { + return Subject.Create>( + Observer.Create>(x => + { + if (x.HasValue) + { + o.SetValue(property, x.Value, priority); + } + }), + o.GetBindingObservable(property)); + } +} diff --git a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs b/src/Avalonia.ReactiveUI/AvaloniaScheduler.cs similarity index 98% rename from src/Avalonia.Base/Threading/AvaloniaScheduler.cs rename to src/Avalonia.ReactiveUI/AvaloniaScheduler.cs index 6423d86e7c..61c503e7a5 100644 --- a/src/Avalonia.Base/Threading/AvaloniaScheduler.cs +++ b/src/Avalonia.ReactiveUI/AvaloniaScheduler.cs @@ -1,8 +1,9 @@ using System; using System.Reactive.Concurrency; using System.Reactive.Disposables; +using Avalonia.Threading; -namespace Avalonia.Threading +namespace Avalonia.ReactiveUI { /// /// A reactive scheduler that uses Avalonia's . diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index ab543703d9..fb52b3b147 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj index 1dd6426b39..4aa6b66743 100644 --- a/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj +++ b/src/Avalonia.Themes.Simple/Avalonia.Themes.Simple.csproj @@ -13,7 +13,6 @@ - diff --git a/src/Avalonia.X11/Glx/GlxContext.cs b/src/Avalonia.X11/Glx/GlxContext.cs index def5228e94..88006f0b47 100644 --- a/src/Avalonia.X11/Glx/GlxContext.cs +++ b/src/Avalonia.X11/Glx/GlxContext.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Threading; using Avalonia.OpenGL; +using Avalonia.Reactive; namespace Avalonia.X11.Glx { class GlxContext : IGlContext diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 810a806c8a..5bbcf3af8c 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using Avalonia.Reactive; using System.Text; using System.Threading.Tasks; using System.Threading; diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 267bd2520e..da739c754c 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -66,7 +66,6 @@ - diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs index 45e23db84f..170a31fd64 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/FindVisualAncestorNode.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Data.Core; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs index 77ffa24687..cb21df0507 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/ObservableStreamPlugin.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; using System.Text; using Avalonia.Data.Core.Plugins; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs index ef11b06369..138e8ec671 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs @@ -5,6 +5,7 @@ using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Data.Core.Plugins; using Avalonia.Utilities; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs index 8489dd9d19..7c9f7559c6 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/TaskStreamPlugin.cs @@ -1,10 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Linq; -using System.Reactive.Subjects; using System.Threading.Tasks; using Avalonia.Data; using Avalonia.Data.Core.Plugins; +using Avalonia.Reactive; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { @@ -30,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings case TaskStatus.Faulted: return HandleCompleted(task); default: - var subject = new Subject(); + var subject = new LightweightSubject(); task.ContinueWith( x => HandleCompleted(task).Subscribe(subject), TaskScheduler.FromCurrentSynchronizationContext()) diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs index 07c79d7077..23166170db 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/TemplateContent.cs @@ -7,7 +7,6 @@ namespace Avalonia.Markup.Xaml.Templates public static class TemplateContent { public static ControlTemplateResult Load(object templateContent) - { if (templateContent is Func direct) { diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 753d577105..e3878b5bc6 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -12,7 +12,6 @@ - diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 6f836a799a..66907f33d0 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -1,15 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; using Avalonia.Controls; -using Avalonia.Data.Converters; using Avalonia.Data.Core; -using Avalonia.LogicalTree; using Avalonia.Markup.Parsers; -using Avalonia.Reactive; -using Avalonia.VisualTree; namespace Avalonia.Data { diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index c035f0b05d..56aa930869 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -1,9 +1,5 @@ - using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reactive; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Data.Converters; using Avalonia.Data.Core; @@ -247,6 +243,7 @@ namespace Avalonia.Data // Content property is bound to a value which becomes the ContentPresenter's // DataContext - it is from this that the child hosted by the ContentPresenter needs to // inherit its DataContext. + return target.GetObservable(Visual.VisualParentProperty) .Select(x => { @@ -255,8 +252,9 @@ namespace Avalonia.Data }).Switch(); } - private class UpdateSignal : SingleSubscriberObservableBase + private class UpdateSignal : SingleSubscriberObservableBase { + private static readonly object s_val = new(); private readonly AvaloniaObject _target; private readonly AvaloniaProperty _property; @@ -280,7 +278,7 @@ namespace Avalonia.Data { if (e.Property == _property) { - PublishNext(Unit.Default); + PublishNext(s_val); } } } diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 4693b0c617..1515ff2c90 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Data.Converters; using Avalonia.Metadata; diff --git a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs index 4dcdfb3c0e..4270063f87 100644 --- a/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/TemplateBinding.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Reactive.Subjects; using Avalonia.Data.Converters; using Avalonia.Reactive; using Avalonia.Styling; @@ -13,7 +12,7 @@ namespace Avalonia.Data public class TemplateBinding : SingleSubscriberObservableBase, IBinding, IDescription, - ISubject, + IAvaloniaSubject, ISetterValue { private bool _isSetterValue; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index 6e6a163989..a478af570a 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive; using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.Utilities; @@ -64,7 +63,7 @@ namespace Avalonia.Markup.Parsers public static ExpressionObserver Build( Func rootGetter, string expression, - IObservable update, + IObservable update, bool enableDataValidation = false, string? description = null, Func? typeResolver = null) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs index f9a3a61736..4fc17e440b 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs @@ -2,6 +2,7 @@ using Avalonia.Controls; using Avalonia.Data.Core; using Avalonia.LogicalTree; +using Avalonia.Reactive; namespace Avalonia.Markup.Parsers.Nodes { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs index 697612ca12..ffbd34d492 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs @@ -1,6 +1,7 @@ using System; using Avalonia.Data.Core; using Avalonia.LogicalTree; +using Avalonia.Reactive; namespace Avalonia.Markup.Parsers.Nodes { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs index c420a9df8d..ba1c1edffe 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/StringIndexerNode.cs @@ -5,7 +5,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; -using System.Reactive.Linq; +using Avalonia.Reactive; using System.Reflection; using Avalonia.Data; using Avalonia.Data.Core; diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index 1ae47c8b7a..05fad25f1b 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; using Avalonia.Rendering; diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs index 5bf1272c2f..4b3c7a016d 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/GlRenderTarget.cs @@ -1,5 +1,5 @@ using System; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.OpenGL; using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index a7998353d9..e5fb182a3b 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index fb7831415d..c61c276956 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 6153c7c75c..82fd1109f4 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia.Input; diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs index d535efc5b7..b4002d8bc7 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglContext.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Disposables; using System.Threading; using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Reactive; using Avalonia.Win32.Interop; using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.OpenGl.WglConsts; diff --git a/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs b/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs index 2b8bf6f8bf..fdf36206d0 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeToManagedMenuExporter.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using System.Reactive.Linq; +using Avalonia.Reactive; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Platform; diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 3f220f0f09..a1af5fa82a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Reactive.Disposables; +using Avalonia.Reactive; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs index 32019f4c15..5be25a3d4b 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs @@ -1,8 +1,8 @@ using System; using System.Numerics; -using System.Reactive.Disposables; using System.Threading; using Avalonia.OpenGL.Egl; +using Avalonia.Reactive; using Avalonia.Win32.Interop; using MicroCom.Runtime; diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index ba1bfda949..94e9c0c814 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -2,33 +2,30 @@ using System; using System.Collections.ObjectModel; using System.IO; using System.Linq; -using System.Reactive.Disposables; -using System.Reactive.Linq; +using Avalonia.Reactive; +using Avalonia.Reactive; using Avalonia.Controls.Platform; using Avalonia.Logging; +using Avalonia.Threading; namespace Avalonia.Win32 { internal class WindowsMountedVolumeInfoListener : IDisposable { - private readonly CompositeDisposable _disposables; + private readonly IDisposable _disposable; private bool _beenDisposed = false; private ObservableCollection mountedDrives; public WindowsMountedVolumeInfoListener(ObservableCollection mountedDrives) { this.mountedDrives = mountedDrives; - _disposables = new CompositeDisposable(); - var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1)) - .Subscribe(Poll); + _disposable = DispatcherTimer.Run(Poll, TimeSpan.FromSeconds(1)); - _disposables.Add(pollTimer); - - Poll(0); + Poll(); } - private void Poll(long _) + private bool Poll() { var allDrives = DriveInfo.GetDrives(); @@ -56,13 +53,14 @@ namespace Avalonia.Win32 .ToArray(); if (mountedDrives.SequenceEqual(mountVolInfos)) - return; + return true; else { mountedDrives.Clear(); foreach (var i in mountVolInfos) mountedDrives.Add(i); + return true; } } @@ -72,7 +70,7 @@ namespace Avalonia.Win32 { if (disposing) { - + _disposable.Dispose(); } _beenDisposed = true; } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 547b2cb176..030d6ba215 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -1036,7 +1036,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public async Task Bind_With_Scheduler_Executes_On_Scheduler() + public async Task Bind_With_Scheduler_Executes_On_UI_Thread() { var target = new Class1(); var source = new Subject(); @@ -1047,7 +1047,6 @@ namespace Avalonia.Base.UnitTests .Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId); var services = new TestServices( - scheduler: AvaloniaScheduler.Instance, threadingInterface: threadingInterfaceMock.Object); using (UnitTestApplication.Start(services)) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs index 435980bd9a..e6864e6806 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs @@ -41,7 +41,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Complete_When_Update_Observable_Completes() { - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => 1, o => o, update); var completed = false; @@ -54,7 +54,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Complete_When_Update_Observable_Errors() { - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => 1, o => o, update); var completed = false; @@ -87,7 +87,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Unsubscribe_From_Update_Observable() { var scheduler = new TestScheduler(); - var update = scheduler.CreateColdObservable(); + var update = scheduler.CreateColdObservable(); var data = new { Foo = "foo" }; var target = ExpressionObserver.Create(() => data, o => o.Foo, update); var result = new List(); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 144d9e6668..6554be2e89 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -359,7 +359,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Empty_Expression_Should_Track_Root() { var data = new Class1 { Foo = "foo" }; - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => data.Foo, o => o, update); var result = new List(); @@ -533,7 +533,7 @@ namespace Avalonia.Base.UnitTests.Data.Core var first = new Class1 { Foo = "foo" }; var second = new Class1 { Foo = "bar" }; var root = first; - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => root, o => o.Foo, update); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); @@ -640,7 +640,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void RootGetter_Is_Reevaluated_On_Subscribe() { var data = "foo"; - var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject(), null); + var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject(), null); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); diff --git a/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs b/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs index e69eae43f0..9f7d9854fd 100644 --- a/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs +++ b/tests/Avalonia.Base.UnitTests/Styling/StyleActivatorExtensions.cs @@ -3,6 +3,7 @@ using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Reactive; using Avalonia.Styling.Activators; +using Observable = Avalonia.Reactive.Observable; namespace Avalonia.Base.UnitTests.Styling { @@ -10,12 +11,12 @@ namespace Avalonia.Base.UnitTests.Styling { public static IDisposable Subscribe(this IStyleActivator activator, Action action) { - return activator.ToObservable().Subscribe(action); + return Observable.Subscribe(activator.ToObservable(), action); } public static async Task Take(this IStyleActivator activator, int value) { - return await activator.ToObservable().Take(value); + return await System.Reactive.Linq.Observable.Take(activator.ToObservable(), value); } public static IObservable ToObservable(this IStyleActivator activator) diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj index 03d9332051..57338a1e08 100644 --- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj +++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj @@ -15,4 +15,5 @@ + diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 53c186111d..656c2cbbbc 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -334,7 +334,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Subject; + var result = binding.Initiate(target, TextBox.TextProperty).Value; Assert.IsType(((BindingExpression)result).Converter); } @@ -350,7 +350,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Subject; + var result = binding.Initiate(target, TextBox.TextProperty).Value; Assert.Same(converter.Object, ((BindingExpression)result).Converter); } @@ -367,7 +367,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Bar", }; - var result = binding.Initiate(target, TextBox.TextProperty).Subject; + var result = binding.Initiate(target, TextBox.TextProperty).Value; Assert.Same("foo", ((BindingExpression)result).ConverterParameter); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs index 44829aae1e..45deb97f51 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs @@ -20,7 +20,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: false); - var subject = (BindingExpression)instanced.Subject; + var subject = (BindingExpression)instanced.Value; object result = null; subject.Subscribe(x => result = x); @@ -38,7 +38,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Subject; + var subject = (BindingExpression)instanced.Value; object result = null; subject.Subscribe(x => result = x); @@ -56,7 +56,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { Priority = BindingPriority.Template }; var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Subject; + var subject = (BindingExpression)instanced.Value; object result = null; subject.Subscribe(x => result = x); diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs index cb5c625c35..a7ef2c4e4d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs @@ -84,6 +84,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("fallback", target.Text); } @@ -106,6 +107,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("(null)", target.Text); } @@ -128,6 +130,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("1,2,(unset)", target.Text); } @@ -150,6 +153,7 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, binding); + Assert.NotNull(target.Text); Assert.Equal("1,2,Fallback", target.Text); } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetSubject.cs b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs similarity index 95% rename from tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetSubject.cs rename to tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs index 0f2e8ebc21..81f04dc53e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_GetSubject.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AvaloniaObjectTests_GetSubject.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Linq; using Xunit; -namespace Avalonia.Base.UnitTests +namespace Avalonia.ReactiveUI.UnitTests { public class AvaloniaObjectTests_GetSubject { diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index c421adaf21..2eaa8bf1e5 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -71,7 +71,6 @@ namespace Avalonia.UnitTests IRuntimePlatform platform = null, IPlatformRenderInterface renderInterface = null, IRenderTimer renderLoop = null, - IScheduler scheduler = null, ICursorFactory standardCursorFactory = null, Func theme = null, IPlatformThreadingInterface threadingInterface = null, @@ -91,7 +90,6 @@ namespace Avalonia.UnitTests RenderInterface = renderInterface; FontManagerImpl = fontManagerImpl; TextShaperImpl = textShaperImpl; - Scheduler = scheduler; StandardCursorFactory = standardCursorFactory; Theme = theme; ThreadingInterface = threadingInterface; @@ -110,7 +108,6 @@ namespace Avalonia.UnitTests public IPlatformRenderInterface RenderInterface { get; } public IFontManagerImpl FontManagerImpl { get; } public ITextShaperImpl TextShaperImpl { get; } - public IScheduler Scheduler { get; } public ICursorFactory StandardCursorFactory { get; } public Func Theme { get; } public IPlatformThreadingInterface ThreadingInterface { get; } @@ -149,7 +146,6 @@ namespace Avalonia.UnitTests renderInterface: renderInterface ?? RenderInterface, fontManagerImpl: fontManagerImpl ?? FontManagerImpl, textShaperImpl: textShaperImpl ?? TextShaperImpl, - scheduler: scheduler ?? Scheduler, standardCursorFactory: standardCursorFactory ?? StandardCursorFactory, theme: theme ?? Theme, threadingInterface: threadingInterface ?? ThreadingInterface, diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 03e19359c3..82626877d0 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/tests/Avalonia.UnitTests/UnitTestApplication.cs @@ -66,7 +66,6 @@ namespace Avalonia.UnitTests .Bind().ToConstant(Services.FontManagerImpl) .Bind().ToConstant(Services.TextShaperImpl) .Bind().ToConstant(Services.ThreadingInterface) - .Bind().ToConstant(Services.Scheduler) .Bind().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.WindowingPlatform) .Bind().ToSingleton(); From ccffa6acab883f7ad7d59bf256f1b229627d481e Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 20 Dec 2022 21:44:58 -0500 Subject: [PATCH 076/213] Reuse observer instance where possible --- src/Avalonia.Base/Layout/Layoutable.cs | 16 ++++++---------- src/Avalonia.Base/Media/Brush.cs | 8 +++----- src/Avalonia.Base/Media/DashStyle.cs | 10 ++++------ .../Media/ExperimentalAcrylicMaterial.cs | 8 +++----- src/Avalonia.Base/Media/Geometry.cs | 3 ++- src/Avalonia.Base/Media/ScaleTransform.cs | 12 ++++++++++-- src/Avalonia.Base/Media/SkewTransform.cs | 12 ++++++++++-- src/Avalonia.Base/Media/TranslateTransform.cs | 12 ++++++++++-- src/Avalonia.Controls/DefinitionBase.cs | 8 +++----- src/Avalonia.Controls/Panel.cs | 8 ++++++-- 10 files changed, 57 insertions(+), 40 deletions(-) diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs index 9a899ba084..775b8adddd 100644 --- a/src/Avalonia.Base/Layout/Layoutable.cs +++ b/src/Avalonia.Base/Layout/Layoutable.cs @@ -470,14 +470,12 @@ namespace Avalonia.Layout protected static void AffectsMeasure(params AvaloniaProperty[] properties) where T : Layoutable { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.InvalidateMeasure(); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as T)?.InvalidateMeasure()); foreach (var property in properties) { - property.Changed.Subscribe(Invalidate); + property.Changed.Subscribe(invalidateObserver); } } @@ -493,14 +491,12 @@ namespace Avalonia.Layout protected static void AffectsArrange(params AvaloniaProperty[] properties) where T : Layoutable { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.InvalidateArrange(); - } + var invalidate = new AnonymousObserver( + static e => (e.Sender as T)?.InvalidateArrange()); foreach (var property in properties) { - property.Changed.Subscribe(Invalidate); + property.Changed.Subscribe(invalidate); } } diff --git a/src/Avalonia.Base/Media/Brush.cs b/src/Avalonia.Base/Media/Brush.cs index a1afb458c6..b9a560ad8f 100644 --- a/src/Avalonia.Base/Media/Brush.cs +++ b/src/Avalonia.Base/Media/Brush.cs @@ -104,14 +104,12 @@ namespace Avalonia.Media protected static void AffectsRender(params AvaloniaProperty[] properties) where T : Brush { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty)); foreach (var property in properties) { - property.Changed.Subscribe(e => Invalidate(e)); + property.Changed.Subscribe(invalidateObserver); } } diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs index 92b07191e6..4749bfa401 100644 --- a/src/Avalonia.Base/Media/DashStyle.cs +++ b/src/Avalonia.Base/Media/DashStyle.cs @@ -52,13 +52,11 @@ namespace Avalonia.Media static DashStyle() { - void RaiseInvalidated(AvaloniaPropertyChangedEventArgs e) - { - ((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty); - } + var invalidateObserver = new AnonymousObserver( + static e => ((DashStyle)e.Sender).Invalidated?.Invoke(e.Sender, EventArgs.Empty)); - DashesProperty.Changed.Subscribe(RaiseInvalidated); - OffsetProperty.Changed.Subscribe(RaiseInvalidated); + DashesProperty.Changed.Subscribe(invalidateObserver); + OffsetProperty.Changed.Subscribe(invalidateObserver); } /// diff --git a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs index b7a186dde4..22d5a29870 100644 --- a/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Base/Media/ExperimentalAcrylicMaterial.cs @@ -275,14 +275,12 @@ namespace Avalonia.Media protected static void AffectsRender(params AvaloniaProperty[] properties) where T : ExperimentalAcrylicMaterial { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as T)?.RaiseInvalidated(EventArgs.Empty)); foreach (var property in properties) { - property.Changed.Subscribe(e => Invalidate(e)); + property.Changed.Subscribe(invalidateObserver); } } diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index dd162c1e71..9ef32c7d09 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -118,9 +118,10 @@ namespace Avalonia.Media /// protected static void AffectsGeometry(params AvaloniaProperty[] properties) { + var invalidateObserver = new AnonymousObserver(AffectsGeometryInvalidate); foreach (var property in properties) { - property.Changed.Subscribe(AffectsGeometryInvalidate); + property.Changed.Subscribe(invalidateObserver); } } diff --git a/src/Avalonia.Base/Media/ScaleTransform.cs b/src/Avalonia.Base/Media/ScaleTransform.cs index 86163df471..fca2f4bf2a 100644 --- a/src/Avalonia.Base/Media/ScaleTransform.cs +++ b/src/Avalonia.Base/Media/ScaleTransform.cs @@ -26,8 +26,6 @@ namespace Avalonia.Media /// public ScaleTransform() { - this.GetObservable(ScaleXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(ScaleYProperty).Subscribe(_ => RaiseChanged()); } /// @@ -64,5 +62,15 @@ namespace Avalonia.Media /// Gets the transform's . /// public override Matrix Value => Matrix.CreateScale(ScaleX, ScaleY); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ScaleXProperty || change.Property == ScaleYProperty) + { + RaiseChanged(); + } + } } } diff --git a/src/Avalonia.Base/Media/SkewTransform.cs b/src/Avalonia.Base/Media/SkewTransform.cs index 2de2d734c5..d268bee778 100644 --- a/src/Avalonia.Base/Media/SkewTransform.cs +++ b/src/Avalonia.Base/Media/SkewTransform.cs @@ -26,8 +26,6 @@ namespace Avalonia.Media /// public SkewTransform() { - this.GetObservable(AngleXProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(AngleYProperty).Subscribe(_ => RaiseChanged()); } /// @@ -63,5 +61,15 @@ namespace Avalonia.Media /// Gets the transform's . /// public override Matrix Value => Matrix.CreateSkew(Matrix.ToRadians(AngleX), Matrix.ToRadians(AngleY)); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == AngleXProperty || change.Property == AngleYProperty) + { + RaiseChanged(); + } + } } } diff --git a/src/Avalonia.Base/Media/TranslateTransform.cs b/src/Avalonia.Base/Media/TranslateTransform.cs index 75528a4261..1d0169a62c 100644 --- a/src/Avalonia.Base/Media/TranslateTransform.cs +++ b/src/Avalonia.Base/Media/TranslateTransform.cs @@ -26,8 +26,6 @@ namespace Avalonia.Media /// public TranslateTransform() { - this.GetObservable(XProperty).Subscribe(_ => RaiseChanged()); - this.GetObservable(YProperty).Subscribe(_ => RaiseChanged()); } /// @@ -64,5 +62,15 @@ namespace Avalonia.Media /// Gets the transform's . /// public override Matrix Value => Matrix.CreateTranslation(X, Y); + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == XProperty || change.Property == YProperty) + { + RaiseChanged(); + } + } } } diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index f93f613583..5c35a09f1c 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -806,14 +806,12 @@ namespace Avalonia.Controls /// The properties. protected static void AffectsParentMeasure(params AvaloniaProperty[] properties) { - void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - (e.Sender as DefinitionBase)?.Parent?.InvalidateMeasure(); - } + var invalidateObserver = new AnonymousObserver( + static e => (e.Sender as DefinitionBase)?.Parent?.InvalidateMeasure()); foreach (var property in properties) { - property.Changed.Subscribe(Invalidate); + property.Changed.Subscribe(invalidateObserver); } } } diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs index a643edc420..007d18c813 100644 --- a/src/Avalonia.Controls/Panel.cs +++ b/src/Avalonia.Controls/Panel.cs @@ -87,9 +87,11 @@ namespace Avalonia.Controls protected static void AffectsParentArrange(params AvaloniaProperty[] properties) where TPanel : Panel { + var invalidateObserver = new AnonymousObserver( + static e => AffectsParentArrangeInvalidate(e)); foreach (var property in properties) { - property.Changed.Subscribe(AffectsParentArrangeInvalidate); + property.Changed.Subscribe(invalidateObserver); } } @@ -100,9 +102,11 @@ namespace Avalonia.Controls protected static void AffectsParentMeasure(params AvaloniaProperty[] properties) where TPanel : Panel { + var invalidateObserver = new AnonymousObserver( + static e => AffectsParentMeasureInvalidate(e)); foreach (var property in properties) { - property.Changed.Subscribe(AffectsParentMeasureInvalidate); + property.Changed.Subscribe(invalidateObserver); } } From adbb7dc7c920ed211727b9bad8bdb1d031d20a4b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 20 Dec 2022 22:09:54 -0500 Subject: [PATCH 077/213] Fix reactive references in missed projects --- samples/PlatformSanityChecks/PlatformSanityChecks.csproj | 1 + samples/Previewer/Previewer.csproj | 3 ++- src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj index 5c743aabdb..5f61a08f3c 100644 --- a/samples/PlatformSanityChecks/PlatformSanityChecks.csproj +++ b/samples/PlatformSanityChecks/PlatformSanityChecks.csproj @@ -11,4 +11,5 @@ + diff --git a/samples/Previewer/Previewer.csproj b/samples/Previewer/Previewer.csproj index 2cc84168dc..76c1ba7b69 100644 --- a/samples/Previewer/Previewer.csproj +++ b/samples/Previewer/Previewer.csproj @@ -12,7 +12,8 @@ - + + diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index d40fe1e6f8..e6c13d251f 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -38,6 +38,7 @@ + diff --git a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs index e56efdb4a8..4d3d8dcd97 100644 --- a/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs +++ b/src/Browser/Avalonia.Browser/Skia/BrowserSkiaGpu.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using Avalonia.Platform; using Avalonia.Skia; +using Avalonia.Reactive; namespace Avalonia.Browser.Skia { From bfdd66ac5e89fbf3e12488492ea99cc9d5546fdd Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 20 Dec 2022 23:25:36 -0500 Subject: [PATCH 078/213] Remove System.Reactive from Diagnostics package as well --- src/Avalonia.Base/Reactive/Observable.cs | 9 ++++ .../Reactive/SerialDisposableValue.cs | 35 ++++++++++++ .../Avalonia.Diagnostics.csproj | 1 - .../Diagnostics/DevTools.cs | 17 +++--- .../Diagnostics/ViewModels/EventTreeNode.cs | 1 + .../Diagnostics/ViewModels/LogicalTreeNode.cs | 2 +- .../Diagnostics/ViewModels/MainViewModel.cs | 14 ++--- .../Diagnostics/ViewModels/TreeNode.cs | 18 ++----- .../Diagnostics/ViewModels/VisualTreeNode.cs | 54 +++++++++++-------- .../Views/LayoutExplorerView.axaml.cs | 1 + .../Diagnostics/Views/MainWindow.xaml.cs | 30 ++++++----- 11 files changed, 118 insertions(+), 64 deletions(-) create mode 100644 src/Avalonia.Base/Reactive/SerialDisposableValue.cs diff --git a/src/Avalonia.Base/Reactive/Observable.cs b/src/Avalonia.Base/Reactive/Observable.cs index 5191efa910..1009829429 100644 --- a/src/Avalonia.Base/Reactive/Observable.cs +++ b/src/Avalonia.Base/Reactive/Observable.cs @@ -44,6 +44,15 @@ internal static class Observable }, obs.OnError, obs.OnCompleted)); }); } + + public static IObservable StartWith(this IObservable source, TSource value) + { + return Create(obs => + { + obs.OnNext(value); + return source.Subscribe(obs); + }); + } public static IObservable Where(this IObservable source, Func predicate) { diff --git a/src/Avalonia.Base/Reactive/SerialDisposableValue.cs b/src/Avalonia.Base/Reactive/SerialDisposableValue.cs new file mode 100644 index 0000000000..9eaf6343bf --- /dev/null +++ b/src/Avalonia.Base/Reactive/SerialDisposableValue.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; + +namespace Avalonia.Reactive; + +/// +/// Represents a disposable resource whose underlying disposable resource can be replaced by another disposable resource, causing automatic disposal of the previous underlying disposable resource. +/// +internal sealed class SerialDisposableValue : IDisposable +{ + private IDisposable? _current; + private bool _disposed; + + public IDisposable? Disposable + { + get => _current; + set + { + _current?.Dispose(); + _current = value; + + if (_disposed) + { + _current?.Dispose(); + _current = null; + } + } + } + + public void Dispose() + { + _disposed = true; + _current?.Dispose(); + } +} diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index fe694b5730..65d1bea298 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -23,7 +23,6 @@ - diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 9b4e13959b..e8cdcfa37b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Views; using Avalonia.Input; @@ -72,7 +71,11 @@ namespace Avalonia.Diagnostics { throw new ArgumentNullException(nameof(application)); } - var result = Disposable.Empty; + + var openedDisposable = new SerialDisposableValue(); + var result = new CompositeDisposable(2); + result.Add(openedDisposable); + // Skip if call on Design Mode if (!Avalonia.Controls.Design.IsDesignMode && !s_attachedToApplication) @@ -90,13 +93,15 @@ namespace Avalonia.Diagnostics { s_attachedToApplication = true; - ObservableExtensions.Subscribe(application.InputManager.PreProcess.OfType(), e => + result.Add(application.InputManager.PreProcess.Subscribe(e => { - if (options.Gesture.Matches(e)) + if (e is RawKeyEventArgs keyEventArgs + && keyEventArgs.Type == RawKeyEventType.KeyUp + && options.Gesture.Matches(keyEventArgs)) { - result = Open(application, options, owner); + openedDisposable.Disposable = Open(application, options, owner); } - }); + })); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs index 7da43a51b7..0140281d50 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs @@ -4,6 +4,7 @@ using Avalonia.Diagnostics.Views; using Avalonia.Interactivity; using Avalonia.Threading; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index 7c6246701a..3048431af9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -1,10 +1,10 @@ using System; -using System.Reactive.Disposables; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.LogicalTree; using Lifetimes = Avalonia.Controls.ApplicationLifetimes; using System.Linq; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 52535aa991..3870cad7c5 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,11 +1,11 @@ using System; using System.ComponentModel; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Metadata; using Avalonia.Threading; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { @@ -50,15 +50,15 @@ namespace Avalonia.Diagnostics.ViewModels } else { -#nullable disable - _pointerOverSubscription = InputManager.Instance.PreProcess - .OfType() + _pointerOverSubscription = InputManager.Instance!.PreProcess .Subscribe(e => { - PointerOverRoot = e.Root; - PointerOverElement = e.Root.InputHitTest(e.Position); + if (e is Input.Raw.RawPointerEventArgs pointerEventArgs) + { + PointerOverRoot = pointerEventArgs.Root; + PointerOverElement = pointerEventArgs.Root.InputHitTest(pointerEventArgs.Position); + } }); -#nullable restore } Console = new ConsoleViewModel(UpdateConsoleContext); } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 65bfd7fc36..aafaa096e3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Specialized; -using System.Reactive; -using System.Reactive.Linq; +using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.ViewModels { @@ -28,18 +28,8 @@ namespace Avalonia.Diagnostics.ViewModels { ElementName = control.Name; - var removed = Observable.FromEventPattern( - x => control.DetachedFromLogicalTree += x, - x => control.DetachedFromLogicalTree -= x); - var classesChanged = Observable.FromEventPattern< - NotifyCollectionChangedEventHandler, - NotifyCollectionChangedEventArgs>( - x => control.Classes.CollectionChanged += x, - x => control.Classes.CollectionChanged -= x) - .TakeUntil(removed); - - _classesSubscription = classesChanged.Select(_ => Unit.Default) - .StartWith(Unit.Default) + _classesSubscription = ((IObservable)control.Classes.GetWeakCollectionChangedObservable()) + .StartWith(null) .Subscribe(_ => { if (control.Classes.Count > 0) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index e44b852372..f9fb0d18ef 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -1,12 +1,11 @@ using System; -using System.Reactive.Disposables; -using System.Reactive.Linq; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Styling; using Avalonia.VisualTree; +using Avalonia.Reactive; using Lifetimes = Avalonia.Controls.ApplicationLifetimes; using System.Linq; @@ -61,9 +60,12 @@ namespace Avalonia.Diagnostics.ViewModels IPopupHostProvider popupHostProvider, string? providerName = null) { - return Observable.FromEvent( - x => popupHostProvider.PopupHostChanged += x, - x => popupHostProvider.PopupHostChanged -= x) + return Observable.Create(observer => + { + void Handler(IPopupHost? args) => observer.OnNext(args); + popupHostProvider.PopupHostChanged += Handler; + return Disposable.Create(() => popupHostProvider.PopupHostChanged -= Handler); + }) .StartWith(popupHostProvider.PopupHost) .Select(popupHost => { @@ -80,29 +82,39 @@ namespace Avalonia.Diagnostics.ViewModels { Popup p => GetPopupHostObservable(p), Control c => Observable.CombineLatest( - c.GetObservable(Control.ContextFlyoutProperty), - c.GetObservable(Control.ContextMenuProperty), - c.GetObservable(FlyoutBase.AttachedFlyoutProperty), - c.GetObservable(ToolTipDiagnostics.ToolTipProperty), - c.GetObservable(Button.FlyoutProperty), - (ContextFlyout, ContextMenu, AttachedFlyout, ToolTip, ButtonFlyout) => + new IObservable[] + { + c.GetObservable(Control.ContextFlyoutProperty), + c.GetObservable(Control.ContextMenuProperty), + c.GetObservable(FlyoutBase.AttachedFlyoutProperty), + c.GetObservable(ToolTipDiagnostics.ToolTipProperty), + c.GetObservable(Button.FlyoutProperty) + }) + .Select( + items => { - if (ContextMenu != null) + var contextFlyout = items[0]; + var contextMenu = (ContextMenu?)items[1]; + var attachedFlyout = items[2]; + var toolTip = items[3]; + var buttonFlyout = items[4]; + + if (contextMenu != null) //Note: ContextMenus are special since all the items are added as visual children. //So we don't need to go via Popup - return Observable.Return(new PopupRoot(ContextMenu)); + return Observable.Return(new PopupRoot(contextMenu)); - if (ContextFlyout != null) - return GetPopupHostObservable(ContextFlyout, "ContextFlyout"); + if (contextFlyout != null) + return GetPopupHostObservable(contextFlyout, "ContextFlyout"); - if (AttachedFlyout != null) - return GetPopupHostObservable(AttachedFlyout, "AttachedFlyout"); + if (attachedFlyout != null) + return GetPopupHostObservable(attachedFlyout, "AttachedFlyout"); - if (ToolTip != null) - return GetPopupHostObservable(ToolTip, "ToolTip"); + if (toolTip != null) + return GetPopupHostObservable(toolTip, "ToolTip"); - if (ButtonFlyout != null) - return GetPopupHostObservable(ButtonFlyout, "Flyout"); + if (buttonFlyout != null) + return GetPopupHostObservable(buttonFlyout, "Flyout"); return Observable.Return(null); }) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs index c81e3cadf4..56d8737d79 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Controls.Shapes; using Avalonia.Diagnostics.Controls; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.Views { diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs index 13b8cf5e8a..dbc4c98f78 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; @@ -13,13 +12,13 @@ using Avalonia.Markup.Xaml; using Avalonia.Styling; using Avalonia.Themes.Simple; using Avalonia.VisualTree; +using Avalonia.Reactive; namespace Avalonia.Diagnostics.Views { internal class MainWindow : Window, IStyleHost { - private readonly IDisposable? _keySubscription; - private readonly IDisposable? _pointerSubscription; + private readonly IDisposable? _inputSubscription; private readonly Dictionary _frozenPopupStates; private AvaloniaObject? _root; private PixelPoint _lastPointerPosition; @@ -33,15 +32,19 @@ namespace Avalonia.Diagnostics.Views if (Theme is null && this.FindResource(typeof(Window)) is ControlTheme windowTheme) Theme = windowTheme; - _keySubscription = InputManager.Instance?.Process - .OfType() - .Where(x => x.Type == RawKeyEventType.KeyDown) - .Subscribe(RawKeyDown); - _pointerSubscription = InputManager.Instance?.Process - .OfType() - .Subscribe(x => _lastPointerPosition = ((Visual)x.Root).PointToScreen(x.Position)); - - + _inputSubscription = InputManager.Instance?.Process + .Subscribe(x => + { + if (x is RawPointerEventArgs pointerEventArgs) + { + _lastPointerPosition = ((Visual)x.Root).PointToScreen(pointerEventArgs.Position); + } + else if (x is RawKeyEventArgs keyEventArgs && keyEventArgs.Type == RawKeyEventType.KeyDown) + { + RawKeyDown(keyEventArgs); + } + }); + _frozenPopupStates = new Dictionary(); EventHandler? lh = default; @@ -94,8 +97,7 @@ namespace Avalonia.Diagnostics.Views protected override void OnClosed(EventArgs e) { base.OnClosed(e); - _keySubscription?.Dispose(); - _pointerSubscription?.Dispose(); + _inputSubscription?.Dispose(); foreach (var state in _frozenPopupStates) { From b2cf36fc5969b610cb64014821750af4090493e1 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 11:40:46 +0000 Subject: [PATCH 079/213] make ScrollGestureRecognizer.ScrollStartDistance a property --- .../ScrollGestureRecognizer.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 7bcb81767d..02c7e7e4a3 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -16,6 +16,7 @@ namespace Avalonia.Input.GestureRecognizers private bool _canHorizontallyScroll; private bool _canVerticallyScroll; private int _gestureId; + private int _scrollStartDistance = 30; // Movement per second private Vector _inertia; @@ -38,6 +39,15 @@ namespace Avalonia.Input.GestureRecognizers nameof(CanVerticallyScroll), o => o.CanVerticallyScroll, (o, v) => o.CanVerticallyScroll = v); + + /// + /// Defines the property. + /// + public static readonly DirectProperty ScrollStartDistanceProperty = + AvaloniaProperty.RegisterDirect( + nameof(ScrollStartDistance), + o => o.ScrollStartDistance, + (o, v) => o.ScrollStartDistance = v); /// /// Gets or sets a value indicating whether the content can be scrolled horizontally. @@ -56,6 +66,15 @@ namespace Avalonia.Input.GestureRecognizers get => _canVerticallyScroll; set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); } + + /// + /// Gets or sets a value indicating the distance to move the pointer before scrolling is started + /// + public int ScrollStartDistance + { + get => _scrollStartDistance; + set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); + } public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) @@ -75,9 +94,6 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint = e.GetPosition((Visual?)_target); } } - - // Arbitrary chosen value, probably need to move that to platform settings or something - private const double ScrollStartDistance = 30; // Pixels per second speed that is considered to be the stop of inertial scroll private const double InertialScrollSpeedEnd = 5; From 8e2f9d53461a4478bf1022dc61c437284542ddae Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:06:17 +0000 Subject: [PATCH 080/213] And Pinch gensture --- .../PinchGestureRecognizer.cs | 131 ++++++++++++++++++ .../ScrollGestureRecognizer.cs | 4 +- src/Avalonia.Base/Input/Gestures.cs | 8 ++ src/Avalonia.Base/Input/PinchEventArgs.cs | 24 ++++ 4 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs create mode 100644 src/Avalonia.Base/Input/PinchEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs new file mode 100644 index 0000000000..57bd76c5da --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -0,0 +1,131 @@ +using Avalonia.Input.GestureRecognizers; + +namespace Avalonia.Input +{ + public class PinchGestureRecognizer : StyledElement, IGestureRecognizer + { + private IInputElement? _target; + private IGestureRecognizerActionsDispatcher? _actions; + private float _initialDistance; + private IPointer? _firstContact; + private Point _firstPoint; + private IPointer? _secondContact; + private Point _secondPoint; + private Point _origin; + + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + { + _target = target; + _actions = actions; + + _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + } + + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + PointerPressed(e); + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + public void PointerCaptureLost(IPointer pointer) + { + RemoveContact(pointer); + } + + public void PointerMoved(PointerEventArgs e) + { + if (_target != null && _target is Visual visual) + { + if(_firstContact == e.Pointer) + { + _firstPoint = e.GetPosition(visual); + } + else if (_secondContact == e.Pointer) + { + _secondPoint = e.GetPosition(visual); + } + else + { + return; + } + + if (_firstContact != null && _secondContact != null) + { + var distance = GetDistance(_firstPoint, _secondPoint); + + var scale = distance / _initialDistance; + + _target?.RaiseEvent(new PinchEventArgs(scale, _origin)); + } + } + } + + public void PointerPressed(PointerPressedEventArgs e) + { + if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + { + if (_firstContact == null) + { + _firstContact = e.Pointer; + _firstPoint = e.GetPosition(visual); + + return; + } + else if (_secondContact == null && _firstContact != e.Pointer) + { + _secondContact = e.Pointer; + _secondPoint = e.GetPosition(visual); + } + else + { + return; + } + + if (_firstContact != null && _secondContact != null) + { + _initialDistance = GetDistance(_firstPoint, _secondPoint); + + _origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f); + + _actions!.Capture(_firstContact, this); + _actions!.Capture(_secondContact, this); + } + } + } + + public void PointerReleased(PointerReleasedEventArgs e) + { + RemoveContact(e.Pointer); + } + + private void RemoveContact(IPointer pointer) + { + if (_firstContact == pointer || _secondContact == pointer) + { + if (_secondContact == pointer) + { + _secondContact = null; + } + + if (_firstContact == pointer) + { + _firstContact = _secondContact; + + _secondContact = null; + } + _target?.RaiseEvent(new PinchEndedEventArgs()); + } + } + + private float GetDistance(Point a, Point b) + { + var length = _secondPoint - _firstPoint; + return (float)new Vector(length.X, length.Y).Length; + } + } +} diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 02c7e7e4a3..7dc4ab3f2e 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -68,7 +68,7 @@ namespace Avalonia.Input.GestureRecognizers } /// - /// Gets or sets a value indicating the distance to move the pointer before scrolling is started + /// Gets or sets a value indicating the distance the pointer moves before scrolling is started /// public int ScrollStartDistance { @@ -96,7 +96,7 @@ namespace Avalonia.Input.GestureRecognizers } // Pixels per second speed that is considered to be the stop of inertial scroll - private const double InertialScrollSpeedEnd = 5; + private const double InertialScrollSpeedEnd = 0; public void PointerMoved(PointerEventArgs e) { diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 1ea88fe824..b4d5feaf3b 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -46,6 +46,14 @@ namespace Avalonia.Input private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; + public static readonly RoutedEvent PinchEvent = + RoutedEvent.Register( + "PinchEvent", RoutingStrategies.Bubble, typeof(Gestures)); + + public static readonly RoutedEvent PinchEndedEvent = + RoutedEvent.Register( + "PinchEndedEvent", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent PullGestureEvent = RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); diff --git a/src/Avalonia.Base/Input/PinchEventArgs.cs b/src/Avalonia.Base/Input/PinchEventArgs.cs new file mode 100644 index 0000000000..31c760eb51 --- /dev/null +++ b/src/Avalonia.Base/Input/PinchEventArgs.cs @@ -0,0 +1,24 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class PinchEventArgs : RoutedEventArgs + { + public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent) + { + Scale = scale; + ScaleOrigin = scaleOrigin; + } + + public double Scale { get; } = 1; + + public Point ScaleOrigin { get; } + } + + public class PinchEndedEventArgs : RoutedEventArgs + { + public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent) + { + } + } +} From b9827c39cec113f02c586ad259860ffe8fb8dc21 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:06:28 +0000 Subject: [PATCH 081/213] add Gestures sample page --- samples/ControlCatalog/MainView.xaml | 3 + samples/ControlCatalog/Pages/GesturePage.cs | 214 ++++++++++++++++++ samples/ControlCatalog/Pages/GesturePage.xaml | 117 ++++++++++ 3 files changed, 334 insertions(+) create mode 100644 samples/ControlCatalog/Pages/GesturePage.cs create mode 100644 samples/ControlCatalog/Pages/GesturePage.xaml diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 4a5f5bc96c..9e0fb3f852 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -92,6 +92,9 @@ + + + diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs new file mode 100644 index 0000000000..d5d016ed1a --- /dev/null +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -0,0 +1,214 @@ +using System; +using System.Diagnostics; +using System.Numerics; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.LogicalTree; +using Avalonia.Markup.Xaml; +using Avalonia.Rendering.Composition; +using Avalonia.VisualTree; + +namespace ControlCatalog.Pages +{ + public class GesturePage : UserControl + { + private bool _isInit; + private float _currentScale; + + public GesturePage() + { + this.InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) + { + base.OnAttachedToVisualTree(e); + + if(_isInit) + { + return; + } + + _isInit = true; + + SetPullHandlers(this.Find("TopPullZone"), false); + SetPullHandlers(this.Find("BottomPullZone"), true); + SetPullHandlers(this.Find("RightPullZone"), true); + SetPullHandlers(this.Find("LeftPullZone"), false); + + var image = this.Find("PinchImage"); + SetPinchHandlers(image); + + var reset = this.Find + + From 8a48d58e5b23a36188801bd79ed3298908440adf Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:06:56 +0000 Subject: [PATCH 082/213] remove redundant pointer event handled in pull gesture --- .../PullGestureRecognizer.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index fedd07ec32..23bab13fc8 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -1,15 +1,19 @@ -using Avalonia.Input.GestureRecognizers; +using System; +using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input { public class PullGestureRecognizer : StyledElement, IGestureRecognizer { + internal static int MinPullDetectionSize = 50; + private IInputElement? _target; private IGestureRecognizerActionsDispatcher? _actions; private Point _initialPosition; private int _gestureId; private IPointer? _tracking; private PullDirection _pullDirection; + private bool _pullInProgress; /// /// Defines the property. @@ -31,23 +35,12 @@ namespace Avalonia.Input PullDirection = pullDirection; } + public PullGestureRecognizer() { } + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) { _target = target; _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - } - - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) - { - PointerPressed(e); - } - - private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) - { - PointerReleased(e); } public void PointerCaptureLost(IPointer pointer) @@ -94,6 +87,7 @@ namespace Avalonia.Input break; } + _pullInProgress = true; _target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection)); } } @@ -111,16 +105,16 @@ namespace Avalonia.Input switch (PullDirection) { case PullDirection.TopToBottom: - canPull = position.Y < bounds.Height * 0.1; + canPull = position.Y < Math.Max(MinPullDetectionSize, bounds.Height * 0.1); break; case PullDirection.BottomToTop: - canPull = position.Y > bounds.Height - (bounds.Height * 0.1); + canPull = position.Y > Math.Min(bounds.Height - MinPullDetectionSize, bounds.Height - (bounds.Height * 0.1)); break; case PullDirection.LeftToRight: - canPull = position.X < bounds.Width * 0.1; + canPull = position.X < Math.Max(MinPullDetectionSize, bounds.Width * 0.1); break; case PullDirection.RightToLeft: - canPull = position.X > bounds.Width - (bounds.Width * 0.1); + canPull = position.X > Math.Min(bounds.Width - MinPullDetectionSize, bounds.Width - (bounds.Width * 0.1)); break; } @@ -135,7 +129,7 @@ namespace Avalonia.Input public void PointerReleased(PointerReleasedEventArgs e) { - if (_tracking == e.Pointer) + if (_tracking == e.Pointer && _pullInProgress) { EndPull(); } @@ -145,6 +139,7 @@ namespace Avalonia.Input { _tracking = null; _initialPosition = default; + _pullInProgress = false; _target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); } From f09a901e48f59e5dcb9e46348f2e96a959e20eca Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 21 Dec 2022 18:20:38 +0000 Subject: [PATCH 083/213] cleanup --- samples/ControlCatalog/Pages/GesturePage.cs | 2 -- .../Input/GestureRecognizers/PinchGestureRecognizer.cs | 3 --- 2 files changed, 5 deletions(-) diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs index d5d016ed1a..ee10f21317 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.Numerics; using Avalonia; using Avalonia.Controls; @@ -7,7 +6,6 @@ using Avalonia.Input; using Avalonia.LogicalTree; using Avalonia.Markup.Xaml; using Avalonia.Rendering.Composition; -using Avalonia.VisualTree; namespace ControlCatalog.Pages { diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 57bd76c5da..eea7c3b7d1 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -17,9 +17,6 @@ namespace Avalonia.Input { _target = target; _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); } private void OnPointerPressed(object? sender, PointerPressedEventArgs e) From e0e82960c22809c1c9ff58620704e5294d84b156 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 21 Dec 2022 17:09:21 -0500 Subject: [PATCH 084/213] Fix iOS and Android --- src/Android/Avalonia.Android/AndroidThreadingInterface.cs | 2 +- src/Android/Avalonia.Android/ChoreographerTimer.cs | 2 +- src/Avalonia.Base/Avalonia.Base.csproj | 2 ++ src/iOS/Avalonia.iOS/EaglDisplay.cs | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index de9149e9a1..152076013f 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -1,10 +1,10 @@ using System; -using System.Reactive.Disposables; using System.Threading; using Android.OS; using Avalonia.Platform; +using Avalonia.Reactive; using Avalonia.Threading; using App = Android.App.Application; diff --git a/src/Android/Avalonia.Android/ChoreographerTimer.cs b/src/Android/Avalonia.Android/ChoreographerTimer.cs index 19dc7b4ab6..3545ae8fe1 100644 --- a/src/Android/Avalonia.Android/ChoreographerTimer.cs +++ b/src/Android/Avalonia.Android/ChoreographerTimer.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using System.Threading.Tasks; using Android.OS; using Android.Views; +using Avalonia.Reactive; using Avalonia.Rendering; using Java.Lang; diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index e6c13d251f..777a338b8c 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -52,6 +52,8 @@ + + diff --git a/src/iOS/Avalonia.iOS/EaglDisplay.cs b/src/iOS/Avalonia.iOS/EaglDisplay.cs index 7a5e1a496d..8f7c1f3308 100644 --- a/src/iOS/Avalonia.iOS/EaglDisplay.cs +++ b/src/iOS/Avalonia.iOS/EaglDisplay.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; using Avalonia.OpenGL; using Avalonia.Platform; +using Avalonia.Reactive; using OpenGLES; namespace Avalonia.iOS From 8a78f134c57abf490a755ec2f3db112b9547b0a3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 21 Dec 2022 20:47:28 -0500 Subject: [PATCH 085/213] Remove JetBrains.Annotations from the repo --- Avalonia.sln | 1 - build/JetBrains.Annotations.props | 5 --- nukebuild/DotNetConfigHelper.cs | 1 - .../Animation/AnimationInstance`1.cs | 1 - src/Avalonia.Base/Avalonia.Base.csproj | 1 - src/Avalonia.Base/Contract.cs | 34 ------------------- src/Avalonia.Base/Data/InstancedBinding.cs | 4 +-- .../Input/Raw/RawInputEventArgs.cs | 6 ++-- .../Input/Raw/RawPointerEventArgs.cs | 6 ---- src/Avalonia.Base/Media/DrawingGroup.cs | 7 ++-- src/Avalonia.Base/Media/Typeface.cs | 3 +- src/Avalonia.Base/PixelVector.cs | 2 -- src/Avalonia.Base/Visual.cs | 5 ++- .../Avalonia.Controls.ColorPicker.csproj | 1 - .../Avalonia.Controls.DataGrid.csproj | 1 - .../DataGridColumn.cs | 4 +-- .../DataGridRows.cs | 1 - .../Primitives/DataGridFrozenGrid.cs | 2 -- .../Avalonia.Controls.csproj | 1 - .../Utils/BorderRenderHelper.cs | 1 - src/Avalonia.Controls/WindowBase.cs | 1 - .../LinuxMountedVolumeInfoProvider.cs | 3 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 23 +++++++------ src/Avalonia.Native/Helpers.cs | 1 - .../MacOSMountedVolumeInfoProvider.cs | 1 - src/Avalonia.X11/NativeDialogs/Gtk.cs | 1 - src/Avalonia.X11/X11Info.cs | 1 - src/Avalonia.X11/X11Screens.cs | 1 - src/Avalonia.X11/X11Window.Ime.cs | 3 +- .../DrmOutputOptions.cs | 1 - .../LinuxFramebufferPlatform.cs | 32 ++++++++--------- .../Output/DrmOutput.cs | 3 +- .../CompiledBindings/CommandAccessorPlugin.cs | 4 +-- .../PropertyInfoAccessorFactory.cs | 14 +++----- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 6 ++-- src/Skia/Avalonia.Skia/GlyphRunImpl.cs | 4 +-- .../Avalonia.Direct2D1.csproj | 1 - src/Windows/Avalonia.Win32/Win32Platform.cs | 5 +-- .../WindowsMountedVolumeInfoProvider.cs | 1 - src/iOS/Avalonia.iOS/AvaloniaView.Text.cs | 1 - .../Rendering/CompositorTestsBase.cs | 1 - .../Data/AccessorTestObject.cs | 1 - .../AvaloniaPropertyDictionaryBenchmarks.cs | 2 +- .../TreeViewTests.cs | 1 - .../Xaml/XamlIlTests.cs | 2 -- 45 files changed, 60 insertions(+), 141 deletions(-) delete mode 100644 build/JetBrains.Annotations.props delete mode 100644 src/Avalonia.Base/Contract.cs diff --git a/Avalonia.sln b/Avalonia.sln index e6898131b0..fc42a5d63b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -100,7 +100,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1 build\EmbedXaml.props = build\EmbedXaml.props build\HarfBuzzSharp.props = build\HarfBuzzSharp.props build\ImageSharp.props = build\ImageSharp.props - build\JetBrains.Annotations.props = build\JetBrains.Annotations.props build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props build\Microsoft.CSharp.props = build\Microsoft.CSharp.props build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props diff --git a/build/JetBrains.Annotations.props b/build/JetBrains.Annotations.props deleted file mode 100644 index 7bc12cbd84..0000000000 --- a/build/JetBrains.Annotations.props +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/nukebuild/DotNetConfigHelper.cs b/nukebuild/DotNetConfigHelper.cs index eca1e2684d..9d43261616 100644 --- a/nukebuild/DotNetConfigHelper.cs +++ b/nukebuild/DotNetConfigHelper.cs @@ -1,5 +1,4 @@ using System.Globalization; -using JetBrains.Annotations; using Nuke.Common.Tools.DotNet; // ReSharper disable ReturnValueOfPureMethodIsNotUsed diff --git a/src/Avalonia.Base/Animation/AnimationInstance`1.cs b/src/Avalonia.Base/Animation/AnimationInstance`1.cs index 0881fde988..6a6e69894b 100644 --- a/src/Avalonia.Base/Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Base/Animation/AnimationInstance`1.cs @@ -5,7 +5,6 @@ using Avalonia.Animation.Animators; using Avalonia.Animation.Utils; using Avalonia.Data; using Avalonia.Reactive; -using JetBrains.Annotations; namespace Avalonia.Animation { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 0d3da66f7a..94a9ea8352 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -15,7 +15,6 @@ - diff --git a/src/Avalonia.Base/Contract.cs b/src/Avalonia.Base/Contract.cs deleted file mode 100644 index 27427700ac..0000000000 --- a/src/Avalonia.Base/Contract.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Runtime.CompilerServices; -using JetBrains.Annotations; - -namespace Avalonia -{ - /// - /// A stub of Code Contract's Contract class. - /// - /// - /// It would be nice to use Code Contracts on Avalonia but last time I tried it slowed things - /// to a crawl and often crashed. Instead use the same signature for checking preconditions - /// in the hope that it might become usable at some point. - /// - public static class Contract - { - /// - /// Specifies a precondition. - /// - /// - /// The exception to throw if is false. - /// - /// The precondition. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - [ContractAnnotation("condition:false=>stop")] - public static void Requires(bool condition) where TException : Exception, new() - { - if (!condition) - { - throw new TException(); - } - } - } -} diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index a349486bf8..4a1e2660de 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -28,11 +28,9 @@ namespace Avalonia.Data /// public InstancedBinding(ISubject subject, BindingMode mode, BindingPriority priority) { - Contract.Requires(subject != null); - Mode = mode; Priority = priority; - Value = subject; + Value = subject ?? throw new ArgumentNullException(nameof(subject)); } private InstancedBinding(object? value, BindingMode mode, BindingPriority priority) diff --git a/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs index 3a5ae1340f..cf4d37b6da 100644 --- a/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs @@ -21,11 +21,9 @@ namespace Avalonia.Input.Raw /// The root from which the event originates. public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) { - device = device ?? throw new ArgumentNullException(nameof(device)); - - Device = device; + Device = device ?? throw new ArgumentNullException(nameof(device)); Timestamp = timestamp; - Root = root; + Root = root ?? throw new ArgumentNullException(nameof(root)); } /// diff --git a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs index 854dd4b83b..ebedc9e6e8 100644 --- a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs @@ -53,9 +53,6 @@ namespace Avalonia.Input.Raw RawInputModifiers inputModifiers) : base(device, timestamp, root) { - Contract.Requires(device != null); - Contract.Requires(root != null); - Point = new RawPointerPoint(); Position = position; Type = type; @@ -80,9 +77,6 @@ namespace Avalonia.Input.Raw RawInputModifiers inputModifiers) : base(device, timestamp, root) { - Contract.Requires(device != null); - Contract.Requires(root != null); - Point = point; Type = type; InputModifiers = inputModifiers; diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index e71f568207..4a46d89153 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -461,9 +461,10 @@ namespace Avalonia.Media if (_rootDrawing == null) { - // When a DrawingGroup is set, it should be made the root if - // a root drawing didnt exist. - Contract.Requires(_currentDrawingGroup == null); + if (_currentDrawingGroup != null) + { + throw new NotSupportedException("When a DrawingGroup is set, it should be made the root if a root drawing didnt exist."); + } // If this is the first Drawing being added, avoid creating a DrawingGroup // and set this drawing as the root drawing. This optimizes the common diff --git a/src/Avalonia.Base/Media/Typeface.cs b/src/Avalonia.Base/Media/Typeface.cs index e6047bf96c..1e744c30c8 100644 --- a/src/Avalonia.Base/Media/Typeface.cs +++ b/src/Avalonia.Base/Media/Typeface.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using JetBrains.Annotations; namespace Avalonia.Media { @@ -17,7 +16,7 @@ namespace Avalonia.Media /// The font style. /// The font weight. /// The font stretch. - public Typeface([NotNull] FontFamily fontFamily, + public Typeface(FontFamily fontFamily, FontStyle style = FontStyle.Normal, FontWeight weight = FontWeight.Normal, FontStretch stretch = FontStretch.Normal) diff --git a/src/Avalonia.Base/PixelVector.cs b/src/Avalonia.Base/PixelVector.cs index 79e7b94c21..0e156190a4 100644 --- a/src/Avalonia.Base/PixelVector.cs +++ b/src/Avalonia.Base/PixelVector.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using Avalonia.Animation.Animators; -using JetBrains.Annotations; namespace Avalonia { @@ -135,7 +134,6 @@ namespace Avalonia /// /// The other vector. /// True if vectors are nearly equal. - [Pure] public bool NearlyEquals(PixelVector other) { const float tolerance = float.Epsilon; diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index ddaea01b05..5930df5483 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -367,7 +367,10 @@ namespace Avalonia /// The drawing context. public virtual void Render(DrawingContext context) { - Contract.Requires(context != null); + if (context is null) + { + throw new ArgumentNullException(nameof(context)); + } } /// diff --git a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj index f3f8e4c82c..97f9efe8fa 100644 --- a/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj +++ b/src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index 6369961f0f..efa38e49a7 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -14,7 +14,6 @@ - diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 2b715d6a9f..75101dc487 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -641,7 +641,7 @@ namespace Avalonia.Controls public Control GetCellContent(DataGridRow dataGridRow) { - Contract.Requires(dataGridRow != null); + dataGridRow = dataGridRow ?? throw new ArgumentNullException(nameof(dataGridRow)); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); @@ -659,7 +659,7 @@ namespace Avalonia.Controls public Control GetCellContent(object dataItem) { - Contract.Requires(dataItem != null); + dataItem = dataItem ?? throw new ArgumentNullException(nameof(dataItem)); if (OwningGrid == null) { throw DataGridError.DataGrid.NoOwningGrid(GetType()); diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 6c997f62c1..4d3bccee70 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -16,7 +16,6 @@ using System.Diagnostics; using System.Linq; using Avalonia.Data; using Avalonia.Styling; -using JetBrains.Annotations; namespace Avalonia.Controls { diff --git a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs index 9feca71cda..4fd0ca3d26 100644 --- a/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs +++ b/src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs @@ -26,7 +26,6 @@ namespace Avalonia.Controls.Primitives /// true if the grid is frozen; otherwise, false. The default is true. public static bool GetIsFrozen(Control element) { - Contract.Requires(element != null); return element.GetValue(IsFrozenProperty); } @@ -38,7 +37,6 @@ namespace Avalonia.Controls.Primitives /// is null. public static void SetIsFrozen(Control element, bool value) { - Contract.Requires(element != null); element.SetValue(IsFrozenProperty, value); } } diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 2710ac5cc2..cf4beab6d4 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -7,7 +7,6 @@ - diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs index eb9f38894d..6239a5120d 100644 --- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs +++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs @@ -4,7 +4,6 @@ using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; using Avalonia.Utilities; -using JetBrains.Annotations; namespace Avalonia.Controls.Utils { diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index b71dc6df44..65325aac92 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -8,7 +8,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Platform; -using JetBrains.Annotations; namespace Avalonia.Controls { diff --git a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs index b69ea68a76..4624a9c340 100644 --- a/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs +++ b/src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs @@ -9,8 +9,7 @@ namespace Avalonia.FreeDesktop { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); - return new LinuxMountedVolumeInfoListener(ref mountedDrives!); + return new LinuxMountedVolumeInfoListener(ref mountedDrives); } } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index a5b2ea30cc..6d5925c0ae 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -9,23 +9,23 @@ using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using JetBrains.Annotations; using MicroCom.Runtime; +#nullable enable namespace Avalonia.Native { class AvaloniaNativePlatform : IWindowingPlatform { private readonly IAvaloniaNativeFactory _factory; - private AvaloniaNativePlatformOptions _options; - private AvaloniaNativeGlPlatformGraphics _platformGl; + private AvaloniaNativePlatformOptions? _options; + private AvaloniaNativeGlPlatformGraphics? _platformGl; [DllImport("libAvaloniaNative")] static extern IntPtr CreateAvaloniaNative(); internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice(); - [CanBeNull] internal static Compositor Compositor { get; private set; } - [CanBeNull] internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; } + internal static Compositor? Compositor { get; private set; } + internal static PlatformRenderInterfaceContextManager? RenderInterface { get; private set; } public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { @@ -63,7 +63,7 @@ namespace Avalonia.Native public void SetupApplicationName() { - if (!string.IsNullOrWhiteSpace(Application.Current.Name)) + if (!string.IsNullOrWhiteSpace(Application.Current!.Name)) { _factory.MacOptions.SetApplicationTitle(Application.Current.Name); } @@ -118,10 +118,13 @@ namespace Avalonia.Native AvaloniaLocator.CurrentMutable.Bind().ToConstant(renderLoop); var hotkeys = AvaloniaLocator.Current.GetService(); - hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); - hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); - hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + if (hotkeys is not null) + { + hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); + hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); + } if (_options.UseGpu) { diff --git a/src/Avalonia.Native/Helpers.cs b/src/Avalonia.Native/Helpers.cs index 764ff789dc..6270cd30a0 100644 --- a/src/Avalonia.Native/Helpers.cs +++ b/src/Avalonia.Native/Helpers.cs @@ -1,5 +1,4 @@ using Avalonia.Native.Interop; -using JetBrains.Annotations; namespace Avalonia.Native { diff --git a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs index 92b2915e2e..1907a3d129 100644 --- a/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs +++ b/src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs @@ -72,7 +72,6 @@ namespace Avalonia.Native { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); return new MacOSMountedVolumeInfoListener(mountedDrives); } } diff --git a/src/Avalonia.X11/NativeDialogs/Gtk.cs b/src/Avalonia.X11/NativeDialogs/Gtk.cs index d5eae037a9..4e56ae73cf 100644 --- a/src/Avalonia.X11/NativeDialogs/Gtk.cs +++ b/src/Avalonia.X11/NativeDialogs/Gtk.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Avalonia.Platform.Interop; -using JetBrains.Annotations; // ReSharper disable IdentifierTypo namespace Avalonia.X11.NativeDialogs diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 13dc460f45..72f3bf3137 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -2,7 +2,6 @@ using System; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; -using JetBrains.Annotations; using static Avalonia.X11.XLib; // ReSharper disable UnusedAutoPropertyAccessor.Local namespace Avalonia.X11 diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index ba6029b350..87899b11ed 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Runtime.InteropServices; using Avalonia.Platform; using static Avalonia.X11.XLib; -using JetBrains.Annotations; namespace Avalonia.X11 { diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index 257580a5ec..e6066c7964 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/src/Avalonia.X11/X11Window.Ime.cs @@ -7,7 +7,6 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Platform.Interop; -using JetBrains.Annotations; using static Avalonia.X11.XLib; namespace Avalonia.X11 @@ -206,7 +205,7 @@ namespace Avalonia.X11 // This class is used to attach the text value of the key to an asynchronously dispatched KeyDown event class RawKeyEventArgsWithText : RawKeyEventArgs { - public RawKeyEventArgsWithText([NotNull] IKeyboardDevice device, ulong timestamp, [NotNull] IInputRoot root, + public RawKeyEventArgsWithText(IKeyboardDevice device, ulong timestamp, IInputRoot root, RawKeyEventType type, Key key, RawInputModifiers modifiers, string text) : base(device, timestamp, root, type, key, modifiers) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index ce843952e7..3439f0edae 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -1,6 +1,5 @@ using Avalonia.LinuxFramebuffer.Output; using Avalonia.Media; -using JetBrains.Annotations; namespace Avalonia.LinuxFramebuffer { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 38498951f8..e2e9ec8fb3 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -17,7 +17,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Threading; -using JetBrains.Annotations; +#nullable enable namespace Avalonia.LinuxFramebuffer { @@ -26,9 +26,9 @@ namespace Avalonia.LinuxFramebuffer IOutputBackend _fb; private static readonly Stopwatch St = Stopwatch.StartNew(); internal static uint Timestamp => (uint)St.ElapsedTicks; - public static InternalPlatformThreadingInterface Threading; + public static InternalPlatformThreadingInterface? Threading; - internal static Compositor Compositor { get; private set; } + internal static Compositor? Compositor { get; private set; } LinuxFramebufferPlatform(IOutputBackend backend) @@ -60,7 +60,7 @@ namespace Avalonia.LinuxFramebuffer } - internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend inputBackend) where T : AppBuilderBase, new() + internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend? inputBackend) where T : AppBuilderBase, new() { var platform = new LinuxFramebufferPlatform(outputBackend); builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); @@ -71,8 +71,8 @@ namespace Avalonia.LinuxFramebuffer class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime { private readonly IOutputBackend _fb; - [CanBeNull] private readonly IInputBackend _inputBackend; - private TopLevel _topLevel; + private readonly IInputBackend? _inputBackend; + private TopLevel? _topLevel; private readonly CancellationTokenSource _cts = new CancellationTokenSource(); public CancellationToken Token => _cts.Token; @@ -81,15 +81,15 @@ namespace Avalonia.LinuxFramebuffer _fb = fb; } - public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend input) + public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend? input) { _fb = fb; _inputBackend = input; } - public Control MainView + public Control? MainView { - get => (Control)_topLevel?.Content; + get => (Control?)_topLevel?.Content; set { if (_topLevel == null) @@ -119,8 +119,8 @@ namespace Avalonia.LinuxFramebuffer } public int ExitCode { get; private set; } - public event EventHandler Startup; - public event EventHandler Exit; + public event EventHandler? Startup; + public event EventHandler? Exit; public void Start(string[] args) { @@ -140,19 +140,19 @@ namespace Avalonia.LinuxFramebuffer public static class LinuxFramebufferPlatformExtensions { - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1, IInputBackend inputBackend = default) + public static int StartLinuxFbDev(this T builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend inputBackend = default) + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1, IInputBackend inputBackend = default) + public static int StartLinuxDrm(this T builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null, IInputBackend inputBackend = default) + public static int StartLinuxDrm(this T builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); - public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend inputBackend = default) + public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default) where T : AppBuilderBase, new() { var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index 22dd407791..d61dcd4f91 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -8,7 +8,6 @@ using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Platform.Interop; -using JetBrains.Annotations; using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods; using static Avalonia.LinuxFramebuffer.Output.LibDrm; using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats; @@ -50,7 +49,7 @@ namespace Avalonia.LinuxFramebuffer.Output _outputOptions = options; Init(card, resources, connector, modeInfo); } - public DrmOutput(string path = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) + public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null) { if(options != null) _outputOptions = options; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs index 3084964d44..4478a79d27 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs @@ -44,9 +44,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public CommandAccessor(WeakReference reference, Action execute, Func canExecute, ISet dependsOnProperties) { - Contract.Requires(reference != null); - - _reference = reference; + _reference = reference ?? throw new ArgumentNullException(nameof(reference)); _dependsOnProperties = dependsOnProperties; _command = new Command(reference, execute, canExecute); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs index ef11b06369..81f1224650 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs @@ -28,11 +28,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public AvaloniaPropertyAccessor(WeakReference reference, AvaloniaProperty property) { - Contract.Requires(reference != null); - Contract.Requires(property != null); - - _reference = reference; - _property = property; + _reference = reference ?? throw new ArgumentNullException(nameof(reference));; + _property = property ?? throw new ArgumentNullException(nameof(property));; } public AvaloniaObject Instance @@ -77,11 +74,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings public InpcPropertyAccessor(WeakReference reference, IPropertyInfo property) { - Contract.Requires(reference != null); - Contract.Requires(property != null); - - _reference = reference; - _property = property; + _reference = reference ?? throw new ArgumentNullException(nameof(reference)); + _property = property ?? throw new ArgumentNullException(nameof(property)); } public override Type PropertyType => _property.PropertyType; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index a7bec62366..48e8c761eb 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -10,7 +10,6 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Media.Imaging; -using JetBrains.Annotations; using SkiaSharp; namespace Avalonia.Skia @@ -675,13 +674,14 @@ namespace Avalonia.Skia } } - [CanBeNull] - public object GetFeature(Type t) +#nullable enable + public object? GetFeature(Type t) { if (t == typeof(ISkiaSharpApiLeaseFeature)) return new SkiaLeaseFeature(this); return null; } +#nullable restore /// /// Configure paint wrapper for using gradient brush. diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs index bdc3d075cf..484fd9f219 100644 --- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs @@ -1,8 +1,8 @@ using System; using Avalonia.Metadata; using Avalonia.Platform; -using JetBrains.Annotations; using SkiaSharp; +#nullable enable namespace Avalonia.Skia { @@ -10,7 +10,7 @@ namespace Avalonia.Skia [Unstable] public class GlyphRunImpl : IGlyphRunImpl { - public GlyphRunImpl([NotNull] SKTextBlob textBlob) + public GlyphRunImpl(SKTextBlob textBlob) { TextBlob = textBlob ?? throw new ArgumentNullException (nameof (textBlob)); } diff --git a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj index fb7831415d..5fecaef100 100644 --- a/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj +++ b/src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj @@ -17,7 +17,6 @@ - diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 3f220f0f09..3f16b49772 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -19,11 +19,11 @@ using Avalonia.Threading; using Avalonia.Utilities; using Avalonia.Win32.Input; using Avalonia.Win32.Interop; -using JetBrains.Annotations; using static Avalonia.Win32.Interop.UnmanagedMethods; namespace Avalonia { +#nullable enable public static class Win32ApplicationExtensions { public static T UseWin32( @@ -106,9 +106,10 @@ namespace Avalonia /// /// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu /// - [CanBeNull] public IPlatformGraphics CustomPlatformGraphics { get; set; } + public IPlatformGraphics? CustomPlatformGraphics { get; set; } } } +#nullable restore namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs index e1b5f5a3a0..4f4e0b9293 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs @@ -8,7 +8,6 @@ namespace Avalonia.Win32 { public IDisposable Listen(ObservableCollection mountedDrives) { - Contract.Requires(mountedDrives != null); return new WindowsMountedVolumeInfoListener(mountedDrives); } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs index fb0857e472..a1836f3ce4 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.Text.cs @@ -1,6 +1,5 @@ #nullable enable using Avalonia.Input.TextInput; -using JetBrains.Annotations; using UIKit; namespace Avalonia.iOS; diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs index d407a09b06..7f4e160000 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs @@ -18,7 +18,6 @@ using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; -using JetBrains.Annotations; using Xunit; namespace Avalonia.Base.UnitTests.Rendering; diff --git a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs index 0039f5670c..9ee3b03e0d 100644 --- a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs +++ b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs @@ -1,6 +1,5 @@ using System.ComponentModel; using System.Runtime.CompilerServices; -using JetBrains.Annotations; namespace Avalonia.Benchmarks.Data { diff --git a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs index d43d6bd48b..e160c8dfa8 100644 --- a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs @@ -172,7 +172,7 @@ internal sealed class AvaloniaPropertyValueStoreOld internal class MockProperty : StyledProperty { - public MockProperty([JetBrains.Annotations.NotNull] string name) : base(name, typeof(object), new StyledPropertyMetadata()) + public MockProperty(string name) : base(name, typeof(object), new StyledPropertyMetadata()) { } } diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index cd38bf556a..81936711ef 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -17,7 +17,6 @@ using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; -using JetBrains.Annotations; using Moq; using Xunit; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index 4b32a8cdca..f42f787117 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -15,7 +15,6 @@ using Avalonia.Styling; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; -using JetBrains.Annotations; using Xunit; namespace Avalonia.Markup.Xaml.UnitTests @@ -389,7 +388,6 @@ namespace Avalonia.Markup.Xaml.UnitTests public bool IsPressed { get; set; } public event PropertyChangedEventHandler PropertyChanged; - [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); From b3eab25240033ffac063166652741809a22083d8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 21 Dec 2022 21:00:11 -0500 Subject: [PATCH 086/213] Fix tests build --- tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs index 9ee3b03e0d..cd323ededb 100644 --- a/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs +++ b/tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs @@ -37,7 +37,6 @@ namespace Avalonia.Benchmarks.Data { } - [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); From 586c3350dccc35834117c58006d5aedd76190ad7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 12:54:37 +0000 Subject: [PATCH 087/213] adds tests --- .../ScrollGestureRecognizer.cs | 2 +- .../Input/GesturesTests.cs | 98 +++++++++++++++++++ tests/Avalonia.UnitTests/TouchTestHelper.cs | 53 ++++++++++ 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.UnitTests/TouchTestHelper.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 7dc4ab3f2e..64fe275547 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -96,7 +96,7 @@ namespace Avalonia.Input.GestureRecognizers } // Pixels per second speed that is considered to be the stop of inertial scroll - private const double InertialScrollSpeedEnd = 0; + private const double InertialScrollSpeedEnd = 5; public void PointerMoved(PointerEventArgs e) { diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 508eff5f9d..59085a21ce 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Input.GestureRecognizers; +using Avalonia.Media; using Avalonia.UnitTests; using Xunit; @@ -201,5 +203,101 @@ namespace Avalonia.Base.UnitTests.Input border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt")); border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt")); } + + [Fact] + public void Pinched_Should_Not_Be_Raised_For_Same_Pointer() + { + var touch = new TouchTestHelper(); + + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new PinchGestureRecognizer()); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true); + + var firstPoint = new Point(5, 5); + var secondPoint = new Point(10, 10); + + touch.Down(border, position: firstPoint); + touch.Down(border, position: secondPoint); + touch.Down(border, position: new Point(20, 20)); + + Assert.False(raised); + } + + [Fact] + public void Pinched_Should_Be_Raised_For_Two_Pointers_Moving() + { + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new PinchGestureRecognizer()); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true); + + var firstPoint = new Point(5, 5); + var secondPoint = new Point(10, 10); + + var firstTouch = new TouchTestHelper(); + var secondTouch = new TouchTestHelper(); + + firstTouch.Down(border, position: firstPoint); + secondTouch.Down(border, position: secondPoint); + secondTouch.Move(border, position: new Point(20, 20)); + + Assert.True(raised); + } + + [Fact] + public void Scrolling_Should_Start_After_Start_Distance_Is_Exceded() + { + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new ScrollGestureRecognizer() + { + CanHorizontallyScroll = true, + CanVerticallyScroll = true, + ScrollStartDistance = 50 + }); + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.ScrollGestureEvent, (s, e) => raised = true); + + var firstTouch = new TouchTestHelper(); + + firstTouch.Down(border, position: new Point(5, 5)); + firstTouch.Move(border, position: new Point(20, 20)); + + Assert.False(raised); + + firstTouch.Move(border, position: new Point(70, 20)); + + Assert.True(raised); + } } } diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs new file mode 100644 index 0000000000..db70f570a2 --- /dev/null +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -0,0 +1,53 @@ +using Avalonia.Input; +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.UnitTests +{ + public class TouchTestHelper + { + private readonly Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Touch, true); + private ulong _nextStamp = 1; + private ulong Timestamp() => _nextStamp++; + public IInputElement Captured => _pointer.Captured; + + public void Down(Interactive target, Point position = default, KeyModifiers modifiers = default) + { + Down(target, target, position, modifiers); + } + + public void Down(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + _pointer.Capture((IInputElement)target); + source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (Visual)source, position, Timestamp(), PointerPointProperties.None, + modifiers)); + } + + public void Move(Interactive target, in Point position, KeyModifiers modifiers = default) => Move(target, target, position, modifiers); + + public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default) + { + target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, + Timestamp(), PointerPointProperties.None, modifiers)); + } + + public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default) + => Up(target, target, position, modifiers); + + public void Up(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + source.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None, + modifiers, MouseButton.None)); + _pointer.Capture(null); + } + + public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default) + => Tap(target, target, position, modifiers); + + public void Tap(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) + { + Down(target, source, position, modifiers); + Up(target, source, position, modifiers); + } + } +} From 9f633b84118ea447c54d12d218dcb638c75f586b Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 16:33:20 -0500 Subject: [PATCH 088/213] Fix OnPlatform-like extensions with a single input parameter --- .../AvaloniaXamlIlOptionMarkupExtensionTransformer.cs | 1 + .../MarkupExtensions/OptionsMarkupExtensionTests.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs index 5004e594f7..13de455b96 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs @@ -366,6 +366,7 @@ internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransfor foreach (var branch in ExtensionNodeContainer.Branches) { var next = codeGen.DefineLabel(); + codeGen.Emit(OpCodes.Nop); if (branch.HasContext) { codeGen.Ldloc(context.ContextLocal); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs index 2d1f961743..0c67f385e1 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs @@ -434,7 +434,7 @@ public class OptionsMarkupExtensionTests : XamlTestBase "; + Text='{local:OptionsMarkupExtensionNoServiceProvider OptionB=""Im Option 2"", OptionA=""Im Option 1""}' />"; var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml); From ab64b3347005ad62c980e0b209d9721828288641 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 16:33:45 -0500 Subject: [PATCH 089/213] Remove IRuntimePlatform.OperatingSystem API and replace with trimmable OperatingSystem API --- .../Pages/PlatformInfoPage.xaml.cs | 5 +- .../PlatformInformationViewModel.cs | 4 +- src/Avalonia.Base/Avalonia.Base.csproj | 3 + .../Compatibility/OperatingSystem.cs | 26 +++++++ src/Avalonia.Base/Logging/LogArea.cs | 5 ++ .../Platform/IRuntimePlatform.cs | 19 ----- .../Platform/StandardRuntimePlatform.cs | 40 +--------- .../StandardRuntimePlatformServices.cs | 13 +--- .../AppBuilderDesktopExtensions.cs | 17 +++-- src/Avalonia.OpenGL/Egl/EglInterface.cs | 6 +- .../BrowserRuntimePlatform.cs | 7 +- .../MarkupExtensions/OnFormFactorExtension.cs | 4 +- .../MarkupExtensions/OnPlatformExtension.cs | 43 +++++++---- .../Helpers/PixelFormatHelper.cs | 8 +- .../OnPlatformExtensionTests.cs | 75 ------------------- 15 files changed, 101 insertions(+), 174 deletions(-) create mode 100644 src/Avalonia.Base/Compatibility/OperatingSystem.cs delete mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs index 1f37451782..6325af2688 100644 --- a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs +++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs @@ -1,5 +1,8 @@ -using Avalonia.Controls; +using System; +using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Media.Immutable; using ControlCatalog.ViewModels; namespace ControlCatalog.Pages diff --git a/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs b/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs index e4f6c3ac73..60f1f84cc9 100644 --- a/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs +++ b/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs @@ -1,3 +1,5 @@ +using System; +using System.Runtime.InteropServices; using Avalonia; using Avalonia.Platform; using MiniMvvm; @@ -13,7 +15,7 @@ public class PlatformInformationViewModel : ViewModelBase if (runtimeInfo is { } info) { - if (info.IsBrowser) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("BROWSER"))) { if (info.IsDesktop) { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 94a9ea8352..d6f1542687 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -28,10 +28,13 @@ + + + diff --git a/src/Avalonia.Base/Compatibility/OperatingSystem.cs b/src/Avalonia.Base/Compatibility/OperatingSystem.cs new file mode 100644 index 0000000000..838f7da8b2 --- /dev/null +++ b/src/Avalonia.Base/Compatibility/OperatingSystem.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Compatibility +{ + internal sealed class OperatingSystemEx + { +#if NET6_0_OR_GREATER + public static bool IsWindows() => OperatingSystem.IsWindows(); + public static bool IsMacOS() => OperatingSystem.IsMacOS(); + public static bool IsLinux() => OperatingSystem.IsLinux(); + public static bool IsAndroid() => OperatingSystem.IsAndroid(); + public static bool IsIOS() => OperatingSystem.IsIOS(); + public static bool IsBrowser() => OperatingSystem.IsBrowser(); + public static bool IsOSPlatform(string platform) => OperatingSystem.IsOSPlatform(platform); +#else + public static bool IsWindows() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + public static bool IsMacOS() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + public static bool IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + public static bool IsAndroid() => IsOSPlatform("ANDROID"); + public static bool IsIOS() => IsOSPlatform("IOS"); + public static bool IsBrowser() => IsOSPlatform("BROWSER"); + public static bool IsOSPlatform(string platform) => RuntimeInformation.IsOSPlatform(OSPlatform.Create(platform)); +#endif + } +} diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs index 972a9a1e9d..f15e87da1b 100644 --- a/src/Avalonia.Base/Logging/LogArea.cs +++ b/src/Avalonia.Base/Logging/LogArea.cs @@ -35,6 +35,11 @@ namespace Avalonia.Logging /// public const string Control = nameof(Control); + /// + /// The log event comes from Win32 Platform. + /// + public const string Platform = nameof(Platform); + /// /// The log event comes from Win32 Platform. /// diff --git a/src/Avalonia.Base/Platform/IRuntimePlatform.cs b/src/Avalonia.Base/Platform/IRuntimePlatform.cs index 91d2a1e0cf..64b504c479 100644 --- a/src/Avalonia.Base/Platform/IRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/IRuntimePlatform.cs @@ -23,29 +23,10 @@ namespace Avalonia.Platform [Unstable] public record struct RuntimePlatformInfo { - public OperatingSystemType OperatingSystem { get; set; } - public FormFactorType FormFactor => IsDesktop ? FormFactorType.Desktop : IsMobile ? FormFactorType.Mobile : FormFactorType.Unknown; public bool IsDesktop { get; set; } public bool IsMobile { get; set; } - public bool IsBrowser { get; set; } - public bool IsCoreClr { get; set; } - public bool IsMono { get; set; } - public bool IsDotNetFramework { get; set; } - public bool IsUnix { get; set; } - } - - [Unstable] - public enum OperatingSystemType - { - Unknown, - WinNT, - Linux, - OSX, - Android, - iOS, - Browser } [Unstable] diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs index 4df9e8e917..ddb492adcf 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs @@ -1,6 +1,6 @@ using System; -using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Compatibility; using Avalonia.Platform.Internal; namespace Avalonia.Platform @@ -14,42 +14,10 @@ namespace Avalonia.Platform public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(size); - private static readonly Lazy Info = new(() => + private static readonly Lazy Info = new(() => new RuntimePlatformInfo { - OperatingSystemType os; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - os = OperatingSystemType.OSX; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - os = OperatingSystemType.Linux; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - os = OperatingSystemType.WinNT; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("Android"))) - os = OperatingSystemType.Android; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("iOS"))) - os = OperatingSystemType.iOS; - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("Browser"))) - os = OperatingSystemType.Browser; - else - throw new Exception("Unknown OS platform " + RuntimeInformation.OSDescription); - - // Source: https://github.com/dotnet/runtime/blob/main/src/libraries/Common/tests/TestUtilities/System/PlatformDetection.cs - var isCoreClr = Environment.Version.Major >= 5 || RuntimeInformation.FrameworkDescription.StartsWith(".NET Core", StringComparison.OrdinalIgnoreCase); - var isMonoRuntime = Type.GetType("Mono.Runtime") != null; - var isFramework = !isCoreClr && RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework", StringComparison.OrdinalIgnoreCase); - - return new RuntimePlatformInfo - { - IsCoreClr = isCoreClr, - IsDotNetFramework = isFramework, - IsMono = isMonoRuntime, - - IsDesktop = os is OperatingSystemType.Linux or OperatingSystemType.OSX or OperatingSystemType.WinNT, - IsMobile = os is OperatingSystemType.Android or OperatingSystemType.iOS, - IsUnix = os is OperatingSystemType.Linux or OperatingSystemType.OSX or OperatingSystemType.Android, - IsBrowser = os == OperatingSystemType.Browser, - OperatingSystem = os, - }; + IsDesktop = OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux(), + IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsAndroid() }); diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs index 65d6733399..0a36b4c9dd 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatformServices.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Avalonia.Compatibility; using Avalonia.Platform.Internal; using Avalonia.Platform.Interop; @@ -18,15 +19,9 @@ namespace Avalonia.Platform #if NET6_0_OR_GREATER new Net6Loader() #else - standardPlatform.GetRuntimeInfo().OperatingSystem switch - { - OperatingSystemType.WinNT => (IDynamicLibraryLoader)new Win32Loader(), - OperatingSystemType.OSX => new UnixLoader(), - OperatingSystemType.Linux => new UnixLoader(), - OperatingSystemType.Android => new UnixLoader(), - // iOS, WASM, ... - _ => new NotSupportedLoader() - } + OperatingSystemEx.IsWindows() ? (IDynamicLibraryLoader)new Win32Loader() + : OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux() || OperatingSystemEx.IsAndroid() ? new UnixLoader() + : new NotSupportedLoader() #endif ); } diff --git a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs index 7c4a489328..d89c62c063 100644 --- a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs +++ b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs @@ -1,5 +1,6 @@ +using Avalonia.Compatibility; using Avalonia.Controls; -using Avalonia.Platform; +using Avalonia.Logging; namespace Avalonia { @@ -8,8 +9,6 @@ namespace Avalonia public static TAppBuilder UsePlatformDetect(this TAppBuilder builder) where TAppBuilder : AppBuilderBase, new() { - var os = builder.RuntimePlatform.GetRuntimeInfo().OperatingSystem; - // We don't have the ability to load every assembly right now, so we are // stuck with manual configuration here // Helpers are extracted to separate methods to take the advantage of the fact @@ -17,21 +16,27 @@ namespace Avalonia // Additionally, by having a hard reference to each assembly, // we verify that the assemblies are in the final .deps.json file // so .NET Core knows where to load the assemblies from,. - if (os == OperatingSystemType.WinNT) + if (OperatingSystemEx.IsWindows()) { LoadWin32(builder); LoadSkia(builder); } - else if(os==OperatingSystemType.OSX) + else if(OperatingSystemEx.IsMacOS()) { LoadAvaloniaNative(builder); LoadSkia(builder); } - else + else if (OperatingSystemEx.IsLinux()) { LoadX11(builder); LoadSkia(builder); } + else + { + Logger.TryGet(LogEventLevel.Warning, LogArea.Platform)?.Log(builder, + "Avalonia.Desktop package was referenced on non-desktop platform or it isn't supported"); + } + return builder; } diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index ad4b55a686..a913c05996 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using Avalonia.Compatibility; using Avalonia.Platform; using Avalonia.Platform.Interop; using Avalonia.SourceGenerator; @@ -24,10 +25,9 @@ namespace Avalonia.OpenGL.Egl static Func Load() { - var os = AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem; - if(os == OperatingSystemType.Linux) + if(OperatingSystemEx.IsLinux()) return Load("libEGL.so.1"); - if (os == OperatingSystemType.Android) + if (OperatingSystemEx.IsAndroid()) return Load("libEGL.so"); throw new PlatformNotSupportedException(); diff --git a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs index 0abc7703da..67bb040410 100644 --- a/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs +++ b/src/Browser/Avalonia.Browser/BrowserRuntimePlatform.cs @@ -11,12 +11,11 @@ internal class BrowserRuntimePlatform : StandardRuntimePlatform { private static readonly Lazy Info = new(() => { + var isMobile = AvaloniaModule.IsMobile(); var result = new RuntimePlatformInfo { - IsCoreClr = true, // WASM browser is always CoreCLR - IsBrowser = true, // BrowserRuntimePlatform only runs on Browser. - OperatingSystem = OperatingSystemType.Browser, - IsMobile = AvaloniaModule.IsMobile() + IsMobile = isMobile, + IsDesktop = !isMobile }; return result; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs index 51e09eef71..c2f1100eff 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnFormFactorExtension.cs @@ -6,7 +6,7 @@ using Avalonia.Platform; namespace Avalonia.Markup.Xaml.MarkupExtensions; -public class OnFormFactorExtension : OnFormFactorExtensionBase +public sealed class OnFormFactorExtension : OnFormFactorExtensionBase { public OnFormFactorExtension() { @@ -24,7 +24,7 @@ public class OnFormFactorExtension : OnFormFactorExtensionBase } } -public class OnFormFactorExtension : OnFormFactorExtensionBase> +public sealed class OnFormFactorExtension : OnFormFactorExtensionBase> { public OnFormFactorExtension() { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs index 1ac7a522f1..1c20020978 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/OnPlatformExtension.cs @@ -1,11 +1,11 @@ #nullable enable using System; +using Avalonia.Compatibility; using Avalonia.Metadata; -using Avalonia.Platform; namespace Avalonia.Markup.Xaml.MarkupExtensions; -public class OnPlatformExtension : OnPlatformExtensionBase +public sealed class OnPlatformExtension : OnPlatformExtensionBase { public OnPlatformExtension() { @@ -17,13 +17,13 @@ public class OnPlatformExtension : OnPlatformExtensionBase Default = defaultValue; } - public static bool ShouldProvideOption(IServiceProvider serviceProvider, OperatingSystemType option) + public static bool ShouldProvideOption(string option) { - return serviceProvider.GetService().GetRuntimeInfo().OperatingSystem == option; + return ShouldProvideOptionInternal(option); } } -public class OnPlatformExtension : OnPlatformExtensionBase> +public sealed class OnPlatformExtension : OnPlatformExtensionBase> { public OnPlatformExtension() { @@ -35,9 +35,9 @@ public class OnPlatformExtension : OnPlatformExtensionBase().GetRuntimeInfo().OperatingSystem == option; + return ShouldProvideOptionInternal(option); } } @@ -47,27 +47,44 @@ public abstract class OnPlatformExtensionBase : IAddChild [MarkupExtensionDefaultOption] public TReturn? Default { get; set; } - [MarkupExtensionOption(OperatingSystemType.WinNT)] + [MarkupExtensionOption("WINDOWS")] public TReturn? Windows { get; set; } - [MarkupExtensionOption(OperatingSystemType.OSX)] + [MarkupExtensionOption("OSX")] // ReSharper disable once InconsistentNaming public TReturn? macOS { get; set; } - [MarkupExtensionOption(OperatingSystemType.Linux)] + [MarkupExtensionOption("LINUX")] public TReturn? Linux { get; set; } - [MarkupExtensionOption(OperatingSystemType.Android)] + [MarkupExtensionOption("ANDROID")] public TReturn? Android { get; set; } - [MarkupExtensionOption(OperatingSystemType.iOS)] + [MarkupExtensionOption("IOS")] // ReSharper disable once InconsistentNaming public TReturn? iOS { get; set; } - [MarkupExtensionOption(OperatingSystemType.Browser)] + [MarkupExtensionOption("BROWSER")] public TReturn? Browser { get; set; } // Required for the compiler, will be replaced with actual method compile time. public object ProvideValue() { return this; } void IAddChild.AddChild(TOn child) {} + + private protected static bool ShouldProvideOptionInternal(string option) + { + // Instead of using OperatingSystem.IsOSPlatform(string) we use specific "Is***" methods so whole method can be trimmed by the mono linked. + // Keep in mind it works only with const "option" parameter. + // IsOSPlatform might work better with trimming in the future, so it should be re-visited after .NET 8/9. + return option switch + { + "WINDOWS" => OperatingSystemEx.IsWindows(), + "OSX" => OperatingSystemEx.IsMacOS(), + "LINUX" => OperatingSystemEx.IsLinux(), + "ANDROID" => OperatingSystemEx.IsAndroid(), + "IOS" => OperatingSystemEx.IsIOS(), + "BROWSER" => OperatingSystemEx.IsBrowser(), + _ => OperatingSystemEx.IsOSPlatform(option) + }; + } } diff --git a/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs b/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs index 9236bdee8d..84a1972e01 100644 --- a/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/PixelFormatHelper.cs @@ -1,4 +1,5 @@ -using Avalonia.Platform; +using Avalonia.Compatibility; +using Avalonia.Platform; using SkiaSharp; namespace Avalonia.Skia.Helpers @@ -18,10 +19,7 @@ namespace Avalonia.Skia.Helpers var colorType = format?.ToSkColorType() ?? SKImageInfo.PlatformColorType; // TODO: This looks like some leftover hack - var runtimePlatform = AvaloniaLocator.Current?.GetService(); - var runtime = runtimePlatform?.GetRuntimeInfo(); - - if (runtime?.IsDesktop == true && runtime.Value.OperatingSystem == OperatingSystemType.Linux) + if (OperatingSystemEx.IsLinux()) { colorType = SKColorType.Bgra8888; } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs deleted file mode 100644 index 1d37378010..0000000000 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Platform; -using Xunit; - -namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions; - -public class OnPlatformExtensionTests : XamlTestBase -{ - [Fact] - public void Should_Resolve_Default_Value() - { - using (AvaloniaLocator.EnterScope()) - { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown)); - - var xaml = @" - - -"; - - var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var textBlock = (TextBlock)userControl.Content!; - - Assert.Equal("Hello World", textBlock.Text); - } - } - - [Theory] - [InlineData(OperatingSystemType.WinNT, "Im Windows")] - [InlineData(OperatingSystemType.OSX, "Im macOS")] - [InlineData(OperatingSystemType.Linux, "Im Linux")] - [InlineData(OperatingSystemType.Android, "Im Android")] - [InlineData(OperatingSystemType.iOS, "Im iOS")] - [InlineData(OperatingSystemType.Browser, "Im Browser")] - [InlineData(OperatingSystemType.Unknown, "Default value")] - public void Should_Resolve_Expected_Value_Per_Platform(OperatingSystemType currentPlatform, string expectedResult) - { - using (AvaloniaLocator.EnterScope()) - { - AvaloniaLocator.CurrentMutable.Bind() - .ToConstant(new TestRuntimePlatform(currentPlatform)); - - var xaml = @" - - -"; - - var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); - var textBlock = (TextBlock)userControl.Content!; - - Assert.Equal(expectedResult, textBlock.Text); - } - } - - private class TestRuntimePlatform : StandardRuntimePlatform - { - private readonly OperatingSystemType _operatingSystemType; - - public TestRuntimePlatform(OperatingSystemType operatingSystemType) - { - _operatingSystemType = operatingSystemType; - } - - public override RuntimePlatformInfo GetRuntimeInfo() - { - return new RuntimePlatformInfo() { OperatingSystem = _operatingSystemType }; - } - } -} From c2e00428dff6e0ea326ac993bfbbb71fcfad2909 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 22:41:09 -0500 Subject: [PATCH 090/213] Remove AppBuilderBase and generics --- .../Avalonia.Android/AndroidPlatform.cs | 2 +- .../AvaloniaSplashActivity.cs | 2 +- src/Avalonia.Controls/AppBuilder.cs | 239 ++++++++++++++++- src/Avalonia.Controls/AppBuilderBase.cs | 244 ------------------ .../ClassicDesktopStyleApplicationLifetime.cs | 5 +- src/Avalonia.Controls/LoggingExtensions.cs | 6 +- .../Remote/RemoteDesignerEntryPoint.cs | 6 +- .../AppBuilderDesktopExtensions.cs | 15 +- .../ManagedFileDialogExtensions.cs | 7 +- .../HeadlessVncPlatformExtensions.cs | 5 +- .../AvaloniaHeadlessPlatform.cs | 3 +- .../AvaloniaNativePlatformExtensions.cs | 3 +- .../AppBuilderExtensions.cs | 3 +- src/Avalonia.X11/X11Platform.cs | 2 +- .../BlazorSingleViewLifetime.cs | 7 +- .../BrowserSingleViewLifetime.cs | 12 +- .../LinuxFramebufferPlatform.cs | 30 +-- .../SkiaApplicationExtensions.cs | 3 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 2 +- src/Windows/Avalonia.Win32/Win32Platform.cs | 4 +- src/iOS/Avalonia.iOS/Platform.cs | 2 +- .../Media/Fonts/FontFamilyLoaderTests.cs | 2 +- tests/Avalonia.UnitTests/TestServices.cs | 14 +- 23 files changed, 285 insertions(+), 333 deletions(-) delete mode 100644 src/Avalonia.Controls/AppBuilderBase.cs diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 75856e4b52..01b48ffd89 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -15,7 +15,7 @@ namespace Avalonia { public static class AndroidApplicationExtensions { - public static T UseAndroid(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseAndroid(this AppBuilder builder) { return builder .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android") diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs index 5b5ebd1bd9..ec26ee5599 100644 --- a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -1,6 +1,6 @@ using Android.OS; using AndroidX.AppCompat.App; -using AndroidX.Lifecycle; +using Avalonia.Controls; namespace Avalonia.Android { diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs index 5bcd87162e..cf79fcd1a8 100644 --- a/src/Avalonia.Controls/AppBuilder.cs +++ b/src/Avalonia.Controls/AppBuilder.cs @@ -1,4 +1,8 @@ -using Avalonia.Controls; +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Linq; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Platform; namespace Avalonia @@ -6,15 +10,244 @@ namespace Avalonia /// /// Initializes platform-specific services for an . /// - public sealed class AppBuilder : AppBuilderBase + public class AppBuilder { + private static bool s_setupWasAlreadyCalled; + private Action? _optionsInitializers; + private Func? _appFactory; + private IApplicationLifetime? _lifetime; + + /// + /// Gets or sets the instance. + /// + public IRuntimePlatform RuntimePlatform { get; set; } + + /// + /// Gets or sets a method to call the initialize the runtime platform services (e. g. AssetLoader) + /// + public Action RuntimePlatformServicesInitializer { get; private set; } + + /// + /// Gets the instance being initialized. + /// + public Application? Instance { get; private set; } + + /// + /// Gets the type of the Instance (even if it's not created yet) + /// + public Type? ApplicationType { get; private set; } + + /// + /// Gets or sets a method to call the initialize the windowing subsystem. + /// + public Action? WindowingSubsystemInitializer { get; private set; } + + /// + /// Gets the name of the currently selected windowing subsystem. + /// + public string? WindowingSubsystemName { get; private set; } + + /// + /// Gets or sets a method to call the initialize the windowing subsystem. + /// + public Action? RenderingSubsystemInitializer { get; private set; } + + /// + /// Gets the name of the currently selected rendering subsystem. + /// + public string? RenderingSubsystemName { get; private set; } + + /// + /// Gets or sets a method to call after the is setup. + /// + public Action AfterSetupCallback { get; private set; } = builder => { }; + + + public Action AfterPlatformServicesSetupCallback { get; private set; } = builder => { }; + /// /// Initializes a new instance of the class. /// public AppBuilder() - : base(new StandardRuntimePlatform(), + : this(new StandardRuntimePlatform(), builder => StandardRuntimePlatformServices.Register(builder.ApplicationType?.Assembly)) { } + + /// + /// Initializes a new instance of the class. + /// + protected AppBuilder(IRuntimePlatform platform, Action platformServices) + { + RuntimePlatform = platform; + RuntimePlatformServicesInitializer = () => platformServices(this); + } + + /// + /// Begin configuring an . + /// + /// The subclass of to configure. + /// An instance. + public static AppBuilder Configure() + where TApp : Application, new() + { + return new AppBuilder() + { + ApplicationType = typeof(TApp), + // Needed for CoreRT compatibility + _appFactory = () => new TApp() + }; + } + + /// + /// Begin configuring an . + /// + /// Factory function for . + /// The subclass of to configure. + /// is useful for passing of dependencies to . + /// An instance. + public static AppBuilder Configure(Func appFactory) + where TApp : Application + { + return new AppBuilder() + { + ApplicationType = typeof(TApp), + _appFactory = appFactory + }; + } + + protected AppBuilder Self => this; + + public AppBuilder AfterSetup(Action callback) + { + AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); + return Self; + } + + + public AppBuilder AfterPlatformServicesSetup(Action callback) + { + AfterPlatformServicesSetupCallback = (Action)Delegate.Combine(AfterPlatformServicesSetupCallback, callback); + return Self; + } + + public delegate void AppMainDelegate(Application app, string[] args); + + public void Start(AppMainDelegate main, string[] args) + { + Setup(); + main(Instance!, args); + } + + /// + /// Sets up the platform-specific services for the application, but does not run it. + /// + /// + public AppBuilder SetupWithoutStarting() + { + Setup(); + return Self; + } + + /// + /// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it. + /// + /// + /// + public AppBuilder SetupWithLifetime(IApplicationLifetime lifetime) + { + _lifetime = lifetime; + Setup(); + return Self; + } + + /// + /// Specifies a windowing subsystem to use. + /// + /// The method to call to initialize the windowing subsystem. + /// The name of the windowing subsystem. + /// An instance. + public AppBuilder UseWindowingSubsystem(Action initializer, string name = "") + { + WindowingSubsystemInitializer = initializer; + WindowingSubsystemName = name; + return Self; + } + + /// + /// Specifies a rendering subsystem to use. + /// + /// The method to call to initialize the rendering subsystem. + /// The name of the rendering subsystem. + /// An instance. + public AppBuilder UseRenderingSubsystem(Action initializer, string name = "") + { + RenderingSubsystemInitializer = initializer; + RenderingSubsystemName = name; + return Self; + } + + /// + /// Configures platform-specific options + /// + public AppBuilder With(T options) + { + _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToConstant(options); }; + return Self; + } + + /// + /// Configures platform-specific options + /// + public AppBuilder With(Func options) + { + _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToFunc(options); }; + return Self; + } + + /// + /// Sets up the platform-specific services for the . + /// + private void Setup() + { + if (RuntimePlatformServicesInitializer == null) + { + throw new InvalidOperationException("No runtime platform services configured."); + } + + if (WindowingSubsystemInitializer == null) + { + throw new InvalidOperationException("No windowing system configured."); + } + + if (RenderingSubsystemInitializer == null) + { + throw new InvalidOperationException("No rendering system configured."); + } + + if (_appFactory == null) + { + throw new InvalidOperationException("No Application factory configured."); + } + + if (s_setupWasAlreadyCalled) + { + throw new InvalidOperationException("Setup was already called on one of AppBuilder instances"); + } + + s_setupWasAlreadyCalled = true; + _optionsInitializers?.Invoke(); + RuntimePlatformServicesInitializer(); + RenderingSubsystemInitializer(); + WindowingSubsystemInitializer(); + AfterPlatformServicesSetupCallback(Self); + Instance = _appFactory(); + Instance.ApplicationLifetime = _lifetime; + AvaloniaLocator.CurrentMutable.BindToSelf(Instance); + Instance.RegisterServices(); + Instance.Initialize(); + AfterSetupCallback(Self); + Instance.OnFrameworkInitializationCompleted(); + } } } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs deleted file mode 100644 index a100d38d78..0000000000 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ /dev/null @@ -1,244 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Linq; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Platform; - -namespace Avalonia.Controls -{ - /// - /// Base class for initializing platform-specific services for an . - /// - /// The type of the AppBuilder class itself. - public abstract class AppBuilderBase where TAppBuilder : AppBuilderBase, new() - { - private static bool s_setupWasAlreadyCalled; - private Action? _optionsInitializers; - private Func? _appFactory; - private IApplicationLifetime? _lifetime; - - /// - /// Gets or sets the instance. - /// - public IRuntimePlatform RuntimePlatform { get; set; } - - /// - /// Gets or sets a method to call the initialize the runtime platform services (e. g. AssetLoader) - /// - public Action RuntimePlatformServicesInitializer { get; private set; } - - /// - /// Gets the instance being initialized. - /// - public Application? Instance { get; private set; } - - /// - /// Gets the type of the Instance (even if it's not created yet) - /// - public Type? ApplicationType { get; private set; } - - /// - /// Gets or sets a method to call the initialize the windowing subsystem. - /// - public Action? WindowingSubsystemInitializer { get; private set; } - - /// - /// Gets the name of the currently selected windowing subsystem. - /// - public string? WindowingSubsystemName { get; private set; } - - /// - /// Gets or sets a method to call the initialize the windowing subsystem. - /// - public Action? RenderingSubsystemInitializer { get; private set; } - - /// - /// Gets the name of the currently selected rendering subsystem. - /// - public string? RenderingSubsystemName { get; private set; } - - /// - /// Gets or sets a method to call after the is setup. - /// - public Action AfterSetupCallback { get; private set; } = builder => { }; - - - public Action AfterPlatformServicesSetupCallback { get; private set; } = builder => { }; - - protected AppBuilderBase(IRuntimePlatform platform, Action platformServices) - { - RuntimePlatform = platform; - RuntimePlatformServicesInitializer = () => platformServices((TAppBuilder)this); - } - - /// - /// Begin configuring an . - /// - /// The subclass of to configure. - /// An instance. - public static TAppBuilder Configure() - where TApp : Application, new() - { - return new TAppBuilder() - { - ApplicationType = typeof(TApp), - // Needed for CoreRT compatibility - _appFactory = () => new TApp() - }; - } - - /// - /// Begin configuring an . - /// - /// Factory function for . - /// The subclass of to configure. - /// is useful for passing of dependencies to . - /// An instance. - public static TAppBuilder Configure(Func appFactory) - where TApp : Application - { - return new TAppBuilder() - { - ApplicationType = typeof(TApp), - _appFactory = appFactory - }; - } - - protected TAppBuilder Self => (TAppBuilder)this; - - public TAppBuilder AfterSetup(Action callback) - { - AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback); - return Self; - } - - - public TAppBuilder AfterPlatformServicesSetup(Action callback) - { - AfterPlatformServicesSetupCallback = (Action)Delegate.Combine(AfterPlatformServicesSetupCallback, callback); - return Self; - } - - public delegate void AppMainDelegate(Application app, string[] args); - - public void Start(AppMainDelegate main, string[] args) - { - Setup(); - main(Instance!, args); - } - - /// - /// Sets up the platform-specific services for the application, but does not run it. - /// - /// - public TAppBuilder SetupWithoutStarting() - { - Setup(); - return Self; - } - - /// - /// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it. - /// - /// - /// - public TAppBuilder SetupWithLifetime(IApplicationLifetime lifetime) - { - _lifetime = lifetime; - Setup(); - return Self; - } - - /// - /// Specifies a windowing subsystem to use. - /// - /// The method to call to initialize the windowing subsystem. - /// The name of the windowing subsystem. - /// An instance. - public TAppBuilder UseWindowingSubsystem(Action initializer, string name = "") - { - WindowingSubsystemInitializer = initializer; - WindowingSubsystemName = name; - return Self; - } - - /// - /// Specifies a rendering subsystem to use. - /// - /// The method to call to initialize the rendering subsystem. - /// The name of the rendering subsystem. - /// An instance. - public TAppBuilder UseRenderingSubsystem(Action initializer, string name = "") - { - RenderingSubsystemInitializer = initializer; - RenderingSubsystemName = name; - return Self; - } - - protected virtual bool CheckSetup => true; - - /// - /// Configures platform-specific options - /// - public TAppBuilder With(T options) - { - _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToConstant(options); }; - return Self; - } - - /// - /// Configures platform-specific options - /// - public TAppBuilder With(Func options) - { - _optionsInitializers += () => { AvaloniaLocator.CurrentMutable.Bind().ToFunc(options); }; - return Self; - } - - /// - /// Sets up the platform-specific services for the . - /// - private void Setup() - { - if (RuntimePlatformServicesInitializer == null) - { - throw new InvalidOperationException("No runtime platform services configured."); - } - - if (WindowingSubsystemInitializer == null) - { - throw new InvalidOperationException("No windowing system configured."); - } - - if (RenderingSubsystemInitializer == null) - { - throw new InvalidOperationException("No rendering system configured."); - } - - if (_appFactory == null) - { - throw new InvalidOperationException("No Application factory configured."); - } - - if (s_setupWasAlreadyCalled && CheckSetup) - { - throw new InvalidOperationException("Setup was already called on one of AppBuilder instances"); - } - - s_setupWasAlreadyCalled = true; - _optionsInitializers?.Invoke(); - RuntimePlatformServicesInitializer(); - RenderingSubsystemInitializer(); - WindowingSubsystemInitializer(); - AfterPlatformServicesSetupCallback(Self); - Instance = _appFactory(); - Instance.ApplicationLifetime = _lifetime; - AvaloniaLocator.CurrentMutable.BindToSelf(Instance); - Instance.RegisterServices(); - Instance.Initialize(); - AfterSetupCallback(Self); - Instance.OnFrameworkInitializationCompleted(); - } - } -} diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index 757db96799..fde401fb01 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -200,9 +200,8 @@ namespace Avalonia { public static class ClassicDesktopStyleApplicationLifetimeExtensions { - public static int StartWithClassicDesktopLifetime( - this T builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) - where T : AppBuilderBase, new() + public static int StartWithClassicDesktopLifetime( + this AppBuilder builder, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) { var lifetime = new ClassicDesktopStyleApplicationLifetime() { diff --git a/src/Avalonia.Controls/LoggingExtensions.cs b/src/Avalonia.Controls/LoggingExtensions.cs index ef14d0b477..f909ba8714 100644 --- a/src/Avalonia.Controls/LoggingExtensions.cs +++ b/src/Avalonia.Controls/LoggingExtensions.cs @@ -8,16 +8,14 @@ namespace Avalonia /// /// Logs Avalonia events to the sink. /// - /// The application class type. /// The app builder instance. /// The minimum level to log. /// The areas to log. Valid values are listed in . /// The app builder instance. - public static T LogToTrace( - this T builder, + public static AppBuilder LogToTrace( + this AppBuilder builder, LogEventLevel level = LogEventLevel.Warning, params string[] areas) - where T : AppBuilderBase, new() { Logger.Sink = new TraceLogSink(level, areas); return builder; diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs index 543d07f958..85605ccd9d 100644 --- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs +++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs @@ -134,12 +134,12 @@ namespace Avalonia.DesignerSupport.Remote IAvaloniaRemoteTransportConnection ConfigureApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj); } - class AppInitializer : IAppInitializer where T : AppBuilderBase, new() + class AppInitializer : IAppInitializer { public IAvaloniaRemoteTransportConnection ConfigureApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj) { - var builder = (AppBuilderBase)obj; + var builder = (AppBuilder)obj; if (args.Method == Methods.AvaloniaRemote) builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport)); if (args.Method == Methods.Html) @@ -191,7 +191,7 @@ namespace Avalonia.DesignerSupport.Remote Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}"); var appBuilder = builderMethod.Invoke(null, null); Log($"Initializing application in design mode"); - var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType())); + var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer)); transport = initializer.ConfigureApp(transport, args, appBuilder); s_transport = transport; transport.OnMessage += OnTransportMessage; diff --git a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs index 7c4a489328..195af7b46e 100644 --- a/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs +++ b/src/Avalonia.Desktop/AppBuilderDesktopExtensions.cs @@ -5,8 +5,7 @@ namespace Avalonia { public static class AppBuilderDesktopExtensions { - public static TAppBuilder UsePlatformDetect(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + public static AppBuilder UsePlatformDetect(this AppBuilder builder) { var os = builder.RuntimePlatform.GetRuntimeInfo().OperatingSystem; @@ -35,19 +34,15 @@ namespace Avalonia return builder; } - static void LoadAvaloniaNative(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadAvaloniaNative(AppBuilder builder) => builder.UseAvaloniaNative(); - static void LoadWin32(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadWin32(AppBuilder builder) => builder.UseWin32(); - static void LoadX11(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadX11(AppBuilder builder) => builder.UseX11(); - static void LoadSkia(TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + static void LoadSkia(AppBuilder builder) => builder.UseSkia(); } } diff --git a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs index fe2dd99100..bc5945441c 100644 --- a/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs +++ b/src/Avalonia.Dialogs/ManagedFileDialogExtensions.cs @@ -24,16 +24,15 @@ namespace Avalonia.Dialogs } } - public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() + public static AppBuilder UseManagedSystemDialogs(this AppBuilder builder) { builder.AfterSetup(_ => AvaloniaLocator.CurrentMutable.Bind().ToSingleton>()); return builder; } - public static TAppBuilder UseManagedSystemDialogs(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() where TWindow : Window, new() + public static AppBuilder UseManagedSystemDialogs(this AppBuilder builder) + where TWindow : Window, new() { builder.AfterSetup(_ => AvaloniaLocator.CurrentMutable.Bind().ToSingleton>()); diff --git a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs index cc7d5ef30d..b6bb69b05d 100644 --- a/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs +++ b/src/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs @@ -11,11 +11,10 @@ namespace Avalonia { public static class HeadlessVncPlatformExtensions { - public static int StartWithHeadlessVncPlatform( - this T builder, + public static int StartWithHeadlessVncPlatform( + this AppBuilder builder, string host, int port, string[] args, ShutdownMode shutdownMode = ShutdownMode.OnLastWindowClose) - where T : AppBuilderBase, new() { var tcpServer = new TcpListener(host == null ? IPAddress.Loopback : IPAddress.Parse(host), port); tcpServer.Start(); diff --git a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs index 8da10fa59b..e7cba13ce0 100644 --- a/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs +++ b/src/Avalonia.Headless/AvaloniaHeadlessPlatform.cs @@ -94,8 +94,7 @@ namespace Avalonia.Headless public static class AvaloniaHeadlessPlatformExtensions { - public static T UseHeadless(this T builder, AvaloniaHeadlessPlatformOptions opts) - where T : AppBuilderBase, new() + public static AppBuilder UseHeadless(this AppBuilder builder, AvaloniaHeadlessPlatformOptions opts) { if(opts.UseHeadlessDrawing) builder.UseRenderingSubsystem(HeadlessPlatformRenderInterface.Initialize, "Headless"); diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 189f45d7c8..6613fc09be 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -6,8 +6,7 @@ namespace Avalonia { public static class AvaloniaNativePlatformExtensions { - public static T UseAvaloniaNative(this T builder) - where T : AppBuilderBase, new() + public static AppBuilder UseAvaloniaNative(this AppBuilder builder) { builder.UseWindowingSubsystem(() => { diff --git a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs index 359da3d7c2..3fde580160 100644 --- a/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs +++ b/src/Avalonia.ReactiveUI/AppBuilderExtensions.cs @@ -12,8 +12,7 @@ namespace Avalonia.ReactiveUI /// scheduler, an activation for view fetcher, a template binding hook. Remember /// to call this method if you are using ReactiveUI in your application. /// - public static TAppBuilder UseReactiveUI(this TAppBuilder builder) - where TAppBuilder : AppBuilderBase, new() => + public static AppBuilder UseReactiveUI(this AppBuilder builder) => builder.AfterPlatformServicesSetup(_ => Locator.RegisterResolverCallbackChanged(() => { if (Locator.CurrentMutable is null) diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index e44b5ded14..9692e0a384 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -303,7 +303,7 @@ namespace Avalonia } public static class AvaloniaX11PlatformExtensions { - public static T UseX11(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseX11(this AppBuilder builder) { builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize(AvaloniaLocator.Current.GetService() ?? diff --git a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs index 2432dc29a3..5e101f7f17 100644 --- a/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser.Blazor/BlazorSingleViewLifetime.cs @@ -8,14 +8,13 @@ namespace Avalonia.Browser.Blazor; [SupportedOSPlatform("browser")] public static class WebAppBuilder { - public static T SetupWithSingleViewLifetime( - this T builder) - where T : AppBuilderBase, new() + public static AppBuilder SetupWithSingleViewLifetime( + this AppBuilder builder) { return builder.SetupWithLifetime(new BlazorSingleViewLifetime()); } - public static T UseBlazor(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseBlazor(this AppBuilder builder) { return builder .UseBrowser() diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs index b6c766b2a5..3adcb8e539 100644 --- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs +++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs @@ -2,8 +2,6 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using System.Runtime.Versioning; -using Avalonia.Browser.Skia; -using Avalonia.Platform; namespace Avalonia.Browser; @@ -27,9 +25,8 @@ public class BrowserPlatformOptions [SupportedOSPlatform("browser")] public static class WebAppBuilder { - public static T SetupBrowserApp( - this T builder, string mainDivId) - where T : AppBuilderBase, new() + public static AppBuilder SetupBrowserApp( + this AppBuilder builder, string mainDivId) { var lifetime = new BrowserSingleViewLifetime(); @@ -42,9 +39,8 @@ public static class WebAppBuilder .SetupWithLifetime(lifetime); } - public static T UseBrowser( - this T builder) - where T : AppBuilderBase, new() + public static AppBuilder UseBrowser( + this AppBuilder builder) { return builder .UseWindowingSubsystem(BrowserWindowingPlatform.Register) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index e2e9ec8fb3..4202ba821f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Threading; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Embedding; @@ -12,11 +13,9 @@ using Avalonia.LinuxFramebuffer.Input; using Avalonia.LinuxFramebuffer.Input.EvDev; using Avalonia.LinuxFramebuffer.Input.LibInput; using Avalonia.LinuxFramebuffer.Output; -using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; -using Avalonia.Threading; #nullable enable namespace Avalonia.LinuxFramebuffer @@ -60,7 +59,7 @@ namespace Avalonia.LinuxFramebuffer } - internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend? inputBackend) where T : AppBuilderBase, new() + internal static LinuxFramebufferLifetime Initialize(AppBuilder builder, IOutputBackend outputBackend, IInputBackend? inputBackend) { var platform = new LinuxFramebufferPlatform(outputBackend); builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); @@ -140,20 +139,17 @@ namespace Avalonia.LinuxFramebuffer public static class LinuxFramebufferPlatformExtensions { - public static int StartLinuxFbDev(this T builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); - - public static int StartLinuxDrm(this T builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); - public static int StartLinuxDrm(this T builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); - - public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default) - where T : AppBuilderBase, new() + public static int StartLinuxFbDev(this AppBuilder builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); + public static int StartLinuxFbDev(this AppBuilder builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); + + public static int StartLinuxDrm(this AppBuilder builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); + public static int StartLinuxDrm(this AppBuilder builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default) + => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); + + public static int StartLinuxDirect(this AppBuilder builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default) { var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); builder.SetupWithLifetime(lifetime); diff --git a/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs b/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs index 014298ce83..3e4fd7b385 100644 --- a/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs +++ b/src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs @@ -12,10 +12,9 @@ namespace Avalonia /// /// Enable Skia renderer. /// - /// Builder type. /// Builder. /// Configure builder. - public static T UseSkia(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseSkia(this AppBuilder builder) { return builder.UseRenderingSubsystem(() => SkiaPlatform.Initialize( AvaloniaLocator.Current.GetService() ?? new SkiaOptions()), diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index a5f77230b7..5887ba2172 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -16,7 +16,7 @@ namespace Avalonia { public static class Direct2DApplicationExtensions { - public static T UseDirect2D1(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseDirect2D1(this AppBuilder builder) { builder.UseRenderingSubsystem(Direct2D1.Direct2D1Platform.Initialize, "Direct2D1"); return builder; diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 3f16b49772..93d16b5768 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -26,9 +26,7 @@ namespace Avalonia #nullable enable public static class Win32ApplicationExtensions { - public static T UseWin32( - this T builder) - where T : AppBuilderBase, new() + public static AppBuilder UseWin32(this AppBuilder builder) { return builder.UseWindowingSubsystem( () => Win32.Win32Platform.Initialize( diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index eb0a55734a..63025f7f0a 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -12,7 +12,7 @@ namespace Avalonia { public static class IOSApplicationExtensions { - public static T UseiOS(this T builder) where T : AppBuilderBase, new() + public static AppBuilder UseiOS(this AppBuilder builder) { return builder .UseWindowingSubsystem(iOS.Platform.Register, "iOS") diff --git a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs index 1bb2300724..aa042ffec8 100644 --- a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs @@ -117,7 +117,7 @@ namespace Avalonia.Base.UnitTests.Media.Fonts private static IDisposable StartWithResources(params (string, string)[] assets) { var assetLoader = new MockAssetLoader(assets); - var services = new TestServices(assetLoader: assetLoader, platform: new AppBuilder().RuntimePlatform); + var services = new TestServices(assetLoader: assetLoader, platform: new StandardRuntimePlatform()); return UnitTestApplication.Start(services); } } diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index c421adaf21..8f132433ec 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -20,7 +20,7 @@ namespace Avalonia.UnitTests { public static readonly TestServices StyledWindow = new TestServices( assetLoader: new AssetLoader(), - platform: new AppBuilder().RuntimePlatform, + platform: new StandardRuntimePlatform(), renderInterface: new MockPlatformRenderInterface(), standardCursorFactory: Mock.Of(), theme: () => CreateSimpleTheme(), @@ -169,16 +169,4 @@ namespace Avalonia.UnitTests y => y.Open() == Mock.Of())); } } - - public class AppBuilder : AppBuilderBase - { - public AppBuilder() - : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.Instance?.GetType() - ?.GetTypeInfo().Assembly)) - { - } - - protected override bool CheckSetup => false; - } } From 9f309415c1219228ffb56e5259d17ac8927673a0 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 23 Dec 2022 09:26:43 +0100 Subject: [PATCH 091/213] Allow to process non text TextRuns Add TextEndOfLine to a TextLine's TextRuns --- .../TextCollapsingProperties.cs | 2 +- .../TextFormatting/TextEllipsisHelper.cs | 23 +-- .../Media/TextFormatting/TextFormatterImpl.cs | 68 +++++---- .../Media/TextFormatting/TextLayout.cs | 7 +- .../TextLeadingPrefixCharacterEllipsis.cs | 133 ++++++++++-------- .../Media/TextFormatting/TextLineBreak.cs | 4 +- .../Media/TextFormatting/TextLineImpl.cs | 106 ++++++++++---- .../Media/TextFormatting/TextRun.cs | 2 +- .../TextTrailingCharacterEllipsis.cs | 2 +- .../TextTrailingWordEllipsis.cs | 2 +- src/Avalonia.Controls/Documents/Run.cs | 5 + .../TextFormatting/MultiBufferTextSource.cs | 4 +- .../TextFormatting/TextFormatterTests.cs | 63 +++++++++ .../Media/TextFormatting/TextLineTests.cs | 4 +- 14 files changed, 283 insertions(+), 142 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs index f677617b14..01804e1ce3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCollapsingProperties.cs @@ -21,6 +21,6 @@ namespace Avalonia.Media.TextFormatting /// Collapses given text line. /// /// Text line to collapse. - public abstract List? Collapse(TextLine textLine); + public abstract List? Collapse(TextLine textLine); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index a1b8985b43..574f92568c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -5,9 +5,11 @@ namespace Avalonia.Media.TextFormatting { internal static class TextEllipsisHelper { - public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) + public static List? Collapse(TextLine textLine, TextCollapsingProperties properties, bool isWordEllipsis) { - if (textLine.TextRuns is not List textRuns || textRuns.Count == 0) + var textRuns = textLine.TextRuns; + + if (textRuns == null || textRuns.Count == 0) { return null; } @@ -20,7 +22,7 @@ namespace Avalonia.Media.TextFormatting if (properties.Width < shapedSymbol.GlyphRun.Size.Width) { //Not enough space to fit in the symbol - return new List(0); + return new List(0); } var availableWidth = properties.Width - shapedSymbol.Size.Width; @@ -70,11 +72,11 @@ namespace Avalonia.Media.TextFormatting collapsedLength += measuredLength; - var collapsedRuns = new List(textRuns.Count); + var collapsedRuns = new List(textRuns.Count); if (collapsedLength > 0) { - var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength); + var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength); collapsedRuns.AddRange(splitResult.First); } @@ -84,22 +86,21 @@ namespace Avalonia.Media.TextFormatting return collapsedRuns; } - availableWidth -= currentRun.Size.Width; - + availableWidth -= shapedRun.Size.Width; break; } - case { } drawableRun: + case DrawableTextRun drawableRun: { //The whole run needs to fit into available space if (currentWidth + drawableRun.Size.Width > availableWidth) { - var collapsedRuns = new List(textRuns.Count); + var collapsedRuns = new List(textRuns.Count); if (collapsedLength > 0) { - var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, collapsedLength); + var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, collapsedLength); collapsedRuns.AddRange(splitResult.First); } @@ -109,6 +110,8 @@ namespace Avalonia.Media.TextFormatting return collapsedRuns; } + availableWidth -= drawableRun.Size.Width; + break; } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 93eb4811b9..07aa5e1550 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utilities; @@ -17,20 +16,20 @@ namespace Avalonia.Media.TextFormatting var textWrapping = paragraphProperties.TextWrapping; FlowDirection resolvedFlowDirection; TextLineBreak? nextLineBreak = null; - List drawableTextRuns; + IReadOnlyList textRuns; - var textRuns = FetchTextRuns(textSource, firstTextSourceIndex, + var fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, out var textEndOfLine, out var textSourceLength); if (previousLineBreak?.RemainingRuns != null) { resolvedFlowDirection = previousLineBreak.FlowDirection; - drawableTextRuns = previousLineBreak.RemainingRuns.ToList(); + textRuns = previousLineBreak.RemainingRuns; nextLineBreak = previousLineBreak; } else { - drawableTextRuns = ShapeTextRuns(textRuns, paragraphProperties, out resolvedFlowDirection); + textRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, out resolvedFlowDirection); if (nextLineBreak == null && textEndOfLine != null) { @@ -44,7 +43,7 @@ namespace Avalonia.Media.TextFormatting { case TextWrapping.NoWrap: { - textLine = new TextLineImpl(drawableTextRuns, firstTextSourceIndex, textSourceLength, + textLine = new TextLineImpl(textRuns, firstTextSourceIndex, textSourceLength, paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); textLine.FinalizeLine(); @@ -54,7 +53,7 @@ namespace Avalonia.Media.TextFormatting case TextWrapping.WrapWithOverflow: case TextWrapping.Wrap: { - textLine = PerformTextWrapping(drawableTextRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties, + textLine = PerformTextWrapping(textRuns, firstTextSourceIndex, paragraphWidth, paragraphProperties, resolvedFlowDirection, nextLineBreak); break; } @@ -71,7 +70,7 @@ namespace Avalonia.Media.TextFormatting /// The text run's. /// The length to split at. /// The split text runs. - internal static SplitResult> SplitDrawableRuns(List textRuns, int length) + internal static SplitResult> SplitTextRuns(IReadOnlyList textRuns, int length) { var currentLength = 0; @@ -88,7 +87,7 @@ namespace Avalonia.Media.TextFormatting var firstCount = currentRun.Length >= 1 ? i + 1 : i; - var first = new List(firstCount); + var first = new List(firstCount); if (firstCount > 1) { @@ -102,7 +101,7 @@ namespace Avalonia.Media.TextFormatting if (currentLength + currentRun.Length == length) { - var second = secondCount > 0 ? new List(secondCount) : null; + var second = secondCount > 0 ? new List(secondCount) : null; if (second != null) { @@ -116,13 +115,13 @@ namespace Avalonia.Media.TextFormatting first.Add(currentRun); - return new SplitResult>(first, second); + return new SplitResult>(first, second); } else { secondCount++; - var second = new List(secondCount); + var second = new List(secondCount); if (currentRun is ShapedTextCharacters shapedTextCharacters) { @@ -138,11 +137,11 @@ namespace Avalonia.Media.TextFormatting second.Add(textRuns[i + j]); } - return new SplitResult>(first, second); + return new SplitResult>(first, second); } } - return new SplitResult>(textRuns, null); + return new SplitResult>(textRuns, null); } /// @@ -154,11 +153,11 @@ namespace Avalonia.Media.TextFormatting /// /// A list of shaped text characters. /// - private static List ShapeTextRuns(List textRuns, TextParagraphProperties paragraphProperties, + private static List ShapeTextRuns(List textRuns, TextParagraphProperties paragraphProperties, out FlowDirection resolvedFlowDirection) { var flowDirection = paragraphProperties.FlowDirection; - var drawableTextRuns = new List(); + var shapedRuns = new List(); var biDiData = new BidiData((sbyte)flowDirection); foreach (var textRun in textRuns) @@ -199,13 +198,6 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case DrawableTextRun drawableRun: - { - drawableTextRuns.Add(drawableRun); - - break; - } - case ShapeableTextCharacters shapeableRun: { var groupedRuns = new List(2) { shapeableRun }; @@ -248,14 +240,20 @@ namespace Avalonia.Media.TextFormatting shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); - drawableTextRuns.AddRange(ShapeTogether(groupedRuns, characterBufferReference, length, shaperOptions)); + shapedRuns.AddRange(ShapeTogether(groupedRuns, characterBufferReference, length, shaperOptions)); + + break; + } + default: + { + shapedRuns.Add(currentRun); break; } } } - return drawableTextRuns; + return shapedRuns; } private static IReadOnlyList ShapeTogether( @@ -390,6 +388,10 @@ namespace Avalonia.Media.TextFormatting if (textRun == null) { + textRuns.Add(new TextEndOfParagraph()); + + textSourceLength += TextRun.DefaultTextSourceLength; + break; } @@ -465,7 +467,7 @@ namespace Avalonia.Media.TextFormatting return false; } - private static bool TryMeasureLength(IReadOnlyList textRuns, double paragraphWidth, out int measuredLength) + private static bool TryMeasureLength(IReadOnlyList textRuns, double paragraphWidth, out int measuredLength) { measuredLength = 0; var currentWidth = 0.0; @@ -502,7 +504,7 @@ namespace Avalonia.Media.TextFormatting break; } - case { } drawableTextRun: + case DrawableTextRun drawableTextRun: { if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth) { @@ -510,7 +512,13 @@ namespace Avalonia.Media.TextFormatting } measuredLength += currentRun.Length; - currentWidth += currentRun.Size.Width; + currentWidth += drawableTextRun.Size.Width; + + break; + } + default: + { + measuredLength += currentRun.Length; break; } @@ -553,7 +561,7 @@ namespace Avalonia.Media.TextFormatting /// /// The current line break if the line was explicitly broken. /// The wrapped text line. - private static TextLineImpl PerformTextWrapping(List textRuns, int firstTextSourceIndex, + private static TextLineImpl PerformTextWrapping(IReadOnlyList textRuns, int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak) { @@ -681,7 +689,7 @@ namespace Avalonia.Media.TextFormatting break; } - var splitResult = SplitDrawableRuns(textRuns, measuredLength); + var splitResult = SplitTextRuns(textRuns, measuredLength); var remainingCharacters = splitResult.Second; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f803001481..ef0c726793 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -448,7 +448,7 @@ namespace Avalonia.Media.TextFormatting var textLine = TextFormatter.Current.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, previousLine?.TextLineBreak); - if (textLine == null || textLine.Length == 0 || textLine.TextRuns.Count == 0 && textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + if(textLine == null || textLine.Length == 0) { if (previousLine != null && previousLine.NewLineLength > 0) { @@ -501,6 +501,11 @@ namespace Avalonia.Media.TextFormatting break; } + + if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfParagraph) + { + break; + } } //Make sure the TextLayout always contains at least on empty line diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index 2752af8f0c..9c085eba17 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -39,9 +39,11 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + public override List? Collapse(TextLine textLine) { - if (textLine.TextRuns is not List textRuns || textRuns.Count == 0) + var textRuns = textLine.TextRuns; + + if (textRuns == null || textRuns.Count == 0) { return null; } @@ -52,7 +54,7 @@ namespace Avalonia.Media.TextFormatting if (Width < shapedSymbol.GlyphRun.Size.Width) { - return new List(0); + return new List(0); } // Overview of ellipsis structure @@ -66,92 +68,101 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { case ShapedTextCharacters shapedRun: - { - currentWidth += currentRun.Size.Width; - - if (currentWidth > availableWidth) { - shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength); - - var collapsedRuns = new List(textRuns.Count); + currentWidth += shapedRun.Size.Width; - if (measuredLength > 0) + if (currentWidth > availableWidth) { - List? preSplitRuns = null; - List? postSplitRuns; - - if (_prefixLength > 0) - { - var splitResult = TextFormatterImpl.SplitDrawableRuns(textRuns, - Math.Min(_prefixLength, measuredLength)); + shapedRun.TryMeasureCharacters(availableWidth, out var measuredLength); - collapsedRuns.AddRange(splitResult.First); + var collapsedRuns = new List(textRuns.Count); - preSplitRuns = splitResult.First; - postSplitRuns = splitResult.Second; - } - else + if (measuredLength > 0) { - postSplitRuns = textRuns; - } + IReadOnlyList? preSplitRuns = null; + IReadOnlyList? postSplitRuns; - collapsedRuns.Add(shapedSymbol); + if (_prefixLength > 0) + { + var splitResult = TextFormatterImpl.SplitTextRuns(textRuns, + Math.Min(_prefixLength, measuredLength)); - if (measuredLength <= _prefixLength || postSplitRuns is null) - { - return collapsedRuns; - } + collapsedRuns.AddRange(splitResult.First); - var availableSuffixWidth = availableWidth; + preSplitRuns = splitResult.First; + postSplitRuns = splitResult.Second; + } + else + { + postSplitRuns = textRuns; + } - if (preSplitRuns is not null) - { - foreach (var run in preSplitRuns) + collapsedRuns.Add(shapedSymbol); + + if (measuredLength <= _prefixLength || postSplitRuns is null) { - availableSuffixWidth -= run.Size.Width; + return collapsedRuns; } - } - for (var i = postSplitRuns.Count - 1; i >= 0; i--) - { - var run = postSplitRuns[i]; + var availableSuffixWidth = availableWidth; - switch (run) + if (preSplitRuns is not null) { - case ShapedTextCharacters endShapedRun: + foreach (var run in preSplitRuns) { - if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth, - out var suffixCount, out var suffixWidth)) + if (run is DrawableTextRun drawableTextRun) { - availableSuffixWidth -= suffixWidth; + availableSuffixWidth -= drawableTextRun.Size.Width; + } + } + } - if (suffixCount > 0) + for (var i = postSplitRuns.Count - 1; i >= 0; i--) + { + var run = postSplitRuns[i]; + + switch (run) + { + case ShapedTextCharacters endShapedRun: { - var splitSuffix = - endShapedRun.Split(run.Length - suffixCount); + if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth, + out var suffixCount, out var suffixWidth)) + { + availableSuffixWidth -= suffixWidth; - collapsedRuns.Add(splitSuffix.Second!); - } - } + if (suffixCount > 0) + { + var splitSuffix = + endShapedRun.Split(run.Length - suffixCount); + + collapsedRuns.Add(splitSuffix.Second!); + } + } - break; + break; + } } } } - } - else - { - collapsedRuns.Add(shapedSymbol); + else + { + collapsedRuns.Add(shapedSymbol); + } + + return collapsedRuns; } - return collapsedRuns; - } + availableWidth -= shapedRun.Size.Width; - break; - } - } + break; + } + case DrawableTextRun drawableTextRun: + { + availableWidth -= drawableTextRun.Size.Width; - availableWidth -= currentRun.Size.Width; + break; + } + } runIndex++; } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs index ce35e47fbd..bf26ac5df4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineBreak.cs @@ -5,7 +5,7 @@ namespace Avalonia.Media.TextFormatting public class TextLineBreak { public TextLineBreak(TextEndOfLine? textEndOfLine = null, FlowDirection flowDirection = FlowDirection.LeftToRight, - IReadOnlyList? remainingRuns = null) + IReadOnlyList? remainingRuns = null) { TextEndOfLine = textEndOfLine; FlowDirection = flowDirection; @@ -25,6 +25,6 @@ namespace Avalonia.Media.TextFormatting /// /// Get the remaining runs that were split up by the during the formatting process. /// - public IReadOnlyList? RemainingRuns { get; } + public IReadOnlyList? RemainingRuns { get; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 3241dfd12b..48f5fec2c0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -6,13 +6,13 @@ namespace Avalonia.Media.TextFormatting { internal class TextLineImpl : TextLine { - private readonly List _textRuns; + private IReadOnlyList _textRuns; private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; private TextLineMetrics _textLineMetrics; private readonly FlowDirection _resolvedFlowDirection; - public TextLineImpl(List textRuns, int firstTextSourceIndex, int length, double paragraphWidth, + public TextLineImpl(IReadOnlyList textRuns, int firstTextSourceIndex, int length, double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection = FlowDirection.LeftToRight, TextLineBreak? lineBreak = null, bool hasCollapsed = false) { @@ -86,11 +86,14 @@ namespace Avalonia.Media.TextFormatting foreach (var textRun in _textRuns) { - var offsetY = GetBaselineOffset(this, textRun); + if (textRun is DrawableTextRun drawable) + { + var offsetY = GetBaselineOffset(this, drawable); - textRun.Draw(drawingContext, new Point(currentX, currentY + offsetY)); + drawable.Draw(drawingContext, new Point(currentX, currentY + offsetY)); - currentX += textRun.Size.Width; + currentX += drawable.Size.Width; + } } } @@ -180,7 +183,14 @@ namespace Avalonia.Media.TextFormatting { var lastRun = _textRuns[_textRuns.Count - 1]; - return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.Length, lastRun.Size.Width); + var size = 0.0; + + if (lastRun is DrawableTextRun drawableTextRun) + { + size = drawableTextRun.Size.Width; + } + + return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.Length, size); } // process hit that happens within the line @@ -220,9 +230,16 @@ namespace Avalonia.Media.TextFormatting currentRun = _textRuns[j]; - if (currentDistance + currentRun.Size.Width <= distance) + if(currentRun is not ShapedTextCharacters) + { + continue; + } + + shapedRun = (ShapedTextCharacters)currentRun; + + if (currentDistance + shapedRun.Size.Width <= distance) { - currentDistance += currentRun.Size.Width; + currentDistance += shapedRun.Size.Width; currentPosition -= currentRun.Length; continue; @@ -234,12 +251,19 @@ namespace Avalonia.Media.TextFormatting characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance); - if (i < _textRuns.Count - 1 && currentDistance + currentRun.Size.Width < distance) + if (currentRun is DrawableTextRun drawableTextRun) { - currentDistance += currentRun.Size.Width; + if (i < _textRuns.Count - 1 && currentDistance + drawableTextRun.Size.Width < distance) + { + currentDistance += drawableTextRun.Size.Width; - currentPosition += currentRun.Length; + currentPosition += currentRun.Length; + continue; + } + } + else + { continue; } @@ -249,7 +273,7 @@ namespace Avalonia.Media.TextFormatting return characterHit; } - private static CharacterHit GetRunCharacterHit(DrawableTextRun run, int currentPosition, double distance) + private static CharacterHit GetRunCharacterHit(TextRun run, int currentPosition, double distance) { CharacterHit characterHit; @@ -270,9 +294,9 @@ namespace Avalonia.Media.TextFormatting break; } - default: + case DrawableTextRun drawableTextRun: { - if (distance < run.Size.Width / 2) + if (distance < drawableTextRun.Size.Width / 2) { characterHit = new CharacterHit(currentPosition); } @@ -282,6 +306,10 @@ namespace Avalonia.Media.TextFormatting } break; } + default: + characterHit = new CharacterHit(currentPosition, run.Length); + + break; } return characterHit; @@ -307,7 +335,7 @@ namespace Avalonia.Media.TextFormatting { var i = index; - var rightToLeftWidth = currentRun.Size.Width; + var rightToLeftWidth = shapedRun.Size.Width; while (i + 1 <= _textRuns.Count - 1) { @@ -317,7 +345,7 @@ namespace Avalonia.Media.TextFormatting { i++; - rightToLeftWidth += nextRun.Size.Width; + rightToLeftWidth += nextShapedRun.Size.Width; continue; } @@ -331,7 +359,10 @@ namespace Avalonia.Media.TextFormatting { currentRun = _textRuns[i]; - rightToLeftWidth -= currentRun.Size.Width; + if (currentRun is DrawableTextRun drawable) + { + rightToLeftWidth -= drawable.Size.Width; + } if (currentPosition + currentRun.Length >= characterIndex) { @@ -355,8 +386,13 @@ namespace Avalonia.Media.TextFormatting return Math.Max(0, currentDistance + distance); } + if (currentRun is DrawableTextRun drawableTextRun) + { + currentDistance += drawableTextRun.Size.Width; + } + //No hit hit found so we add the full width - currentDistance += currentRun.Size.Width; + currentPosition += currentRun.Length; remainingLength -= currentRun.Length; } @@ -380,8 +416,12 @@ namespace Avalonia.Media.TextFormatting return Math.Max(0, currentDistance - distance); } + if (currentRun is DrawableTextRun drawableTextRun) + { + currentDistance -= drawableTextRun.Size.Width; + } + //No hit hit found so we add the full width - currentDistance -= currentRun.Size.Width; currentPosition += currentRun.Length; remainingLength -= currentRun.Length; } @@ -391,7 +431,7 @@ namespace Avalonia.Media.TextFormatting } private static bool TryGetDistanceFromCharacterHit( - DrawableTextRun currentRun, + TextRun currentRun, CharacterHit characterHit, int currentPosition, int remainingLength, @@ -432,7 +472,7 @@ namespace Avalonia.Media.TextFormatting break; } - default: + case DrawableTextRun drawableTextRun: { if (characterIndex == currentPosition) { @@ -441,7 +481,7 @@ namespace Avalonia.Media.TextFormatting if (characterIndex == currentPosition + currentRun.Length) { - distance = currentRun.Size.Width; + distance = drawableTextRun.Size.Width; return true; @@ -449,6 +489,10 @@ namespace Avalonia.Media.TextFormatting break; } + default: + { + return false; + } } return false; @@ -943,7 +987,7 @@ namespace Avalonia.Media.TextFormatting return this; } - private static sbyte GetRunBidiLevel(DrawableTextRun run, FlowDirection flowDirection) + private static sbyte GetRunBidiLevel(TextRun run, FlowDirection flowDirection) { if (run is ShapedTextCharacters shapedTextCharacters) { @@ -1039,16 +1083,18 @@ namespace Avalonia.Media.TextFormatting minLevelToReverse--; } - _textRuns.Clear(); + var textRuns = new List(_textRuns.Count); current = orderedRun; while (current != null) { - _textRuns.Add(current.Run); + textRuns.Add(current.Run); current = current.Next; } + + _textRuns = textRuns; } /// @@ -1286,7 +1332,7 @@ namespace Avalonia.Media.TextFormatting { var runIndex = 0; textPosition = FirstTextSourceIndex; - DrawableTextRun? previousRun = null; + TextRun? previousRun = null; while (runIndex < _textRuns.Count) { @@ -1346,7 +1392,6 @@ namespace Avalonia.Media.TextFormatting break; } - default: { if (codepointIndex == textPosition) @@ -1363,6 +1408,7 @@ namespace Avalonia.Media.TextFormatting break; } + } runIndex++; @@ -1436,7 +1482,7 @@ namespace Avalonia.Media.TextFormatting break; } - case { } drawableTextRun: + case DrawableTextRun drawableTextRun: { widthIncludingWhitespace += drawableTextRun.Size.Width; @@ -1558,7 +1604,7 @@ namespace Avalonia.Media.TextFormatting private sealed class OrderedBidiRun { - public OrderedBidiRun(DrawableTextRun run, sbyte level) + public OrderedBidiRun(TextRun run, sbyte level) { Run = run; Level = level; @@ -1566,7 +1612,7 @@ namespace Avalonia.Media.TextFormatting public sbyte Level { get; } - public DrawableTextRun Run { get; } + public TextRun Run { get; } public OrderedBidiRun? Next { get; set; } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs index 0306054767..b55a0d627b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs @@ -40,7 +40,7 @@ namespace Avalonia.Media.TextFormatting { unsafe { - var characterBuffer = _textRun.CharacterBufferReference.CharacterBuffer; + var characterBuffer = new CharacterBufferRange(_textRun.CharacterBufferReference, _textRun.Length); fixed (char* charsPtr = characterBuffer.Span) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs index 1de04ad061..deecbbe476 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingCharacterEllipsis.cs @@ -26,7 +26,7 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + public override List? Collapse(TextLine textLine) { return TextEllipsisHelper.Collapse(textLine, this, false); } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs index 7c94715aa4..c291e1dfb9 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextTrailingWordEllipsis.cs @@ -31,7 +31,7 @@ namespace Avalonia.Media.TextFormatting /// public override TextRun Symbol { get; } - public override List? Collapse(TextLine textLine) + public override List? Collapse(TextLine textLine) { return TextEllipsisHelper.Collapse(textLine, this, true); } diff --git a/src/Avalonia.Controls/Documents/Run.cs b/src/Avalonia.Controls/Documents/Run.cs index 5d7b8998e6..f64cf79127 100644 --- a/src/Avalonia.Controls/Documents/Run.cs +++ b/src/Avalonia.Controls/Documents/Run.cs @@ -54,6 +54,11 @@ namespace Avalonia.Controls.Documents { var text = Text ?? ""; + if (string.IsNullOrEmpty(text)) + { + return; + } + var textRunProperties = CreateTextRunProperties(); var textCharacters = new TextCharacters(text, textRunProperties); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs index aa499bb135..81d7b1854b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/MultiBufferTextSource.cs @@ -1,6 +1,4 @@ -using System; -using Avalonia.Media.TextFormatting; -using Avalonia.Utilities; +using Avalonia.Media.TextFormatting; namespace Avalonia.Skia.UnitTests.Media.TextFormatting { diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 33d4fba5f1..643bf513d9 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -62,6 +62,69 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + private class TextSourceWithDummyRuns : ITextSource + { + private readonly TextRunProperties _properties; + private readonly List> _textRuns; + + public TextSourceWithDummyRuns(TextRunProperties properties) + { + _properties = properties; + + _textRuns = new List> + { + new ValueSpan(0, 5, new TextCharacters("Hello", _properties)), + new ValueSpan(5, 1, new DummyRun()), + new ValueSpan(6, 1, new DummyRun()), + new ValueSpan(7, 6, new TextCharacters(" World", _properties)) + }; + } + + public TextRun GetTextRun(int textSourceIndex) + { + foreach (var run in _textRuns) + { + if (textSourceIndex < run.Start + run.Length) + { + return run.Value; + } + } + + return new TextEndOfParagraph(); + } + + private class DummyRun : TextRun + { + public DummyRun() + { + Length = DefaultTextSourceLength; + } + + public override int Length { get; } + } + } + + [Fact] + public void Should_Format_TextLine_With_Non_Text_TextRuns() + { + using (Start()) + { + var defaultProperties = + new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Black); + + var textSource = new TextSourceWithDummyRuns(defaultProperties); + + var formatter = new TextFormatterImpl(); + + var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + Assert.Equal(5, textLine.TextRuns.Count); + + Assert.Equal(14, textLine.Length); + } + } + [Fact] public void Should_Format_TextRuns_With_TextRunStyles() { diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index d6257a0de8..f8e287d0a7 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -326,7 +326,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } - Assert.Equal(currentDistance, textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length))); + var actualDistance = textLine.GetDistanceFromCharacterHit(new CharacterHit(s_multiLineText.Length)); + + Assert.Equal(currentDistance, actualDistance); } } From d3836a40ea9d63f2923813c3f01a0a8db333e496 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 23 Dec 2022 09:47:56 -0500 Subject: [PATCH 092/213] Remove Lazy usage where it's not needed anymore --- src/Avalonia.Base/Platform/StandardRuntimePlatform.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs index ddb492adcf..48fff4fc32 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs @@ -14,13 +14,13 @@ namespace Avalonia.Platform public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(size); - private static readonly Lazy Info = new(() => new RuntimePlatformInfo + private static readonly RuntimePlatformInfo s_info = new() { IsDesktop = OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux(), IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsAndroid() - }); + }; - public virtual RuntimePlatformInfo GetRuntimeInfo() => Info.Value; + public virtual RuntimePlatformInfo GetRuntimeInfo() => s_info; } } From e5d816c8055e918b0ea86bfaa4506a72ba370b2f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 23 Dec 2022 11:14:34 -0500 Subject: [PATCH 093/213] Fix copy paste condition --- src/Avalonia.Base/Platform/StandardRuntimePlatform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs index 48fff4fc32..8352d794d0 100644 --- a/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs +++ b/src/Avalonia.Base/Platform/StandardRuntimePlatform.cs @@ -17,7 +17,7 @@ namespace Avalonia.Platform private static readonly RuntimePlatformInfo s_info = new() { IsDesktop = OperatingSystemEx.IsWindows() || OperatingSystemEx.IsMacOS() || OperatingSystemEx.IsLinux(), - IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsAndroid() + IsMobile = OperatingSystemEx.IsAndroid() || OperatingSystemEx.IsIOS() }; From 11b0de3481b8e9950b42fee8fc1869def260e397 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 8 Dec 2022 14:28:33 +0000 Subject: [PATCH 094/213] add hold gesture recognizer --- .../HoldGestureRecognizer.cs | 124 ++++++++++++++++++ src/Avalonia.Base/Input/Gestures.cs | 4 + .../Input/HoldGestureEventArgs.cs | 31 +++++ src/Avalonia.Controls/Control.cs | 19 +++ 4 files changed, 178 insertions(+) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs create mode 100644 src/Avalonia.Base/Input/HoldGestureEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs new file mode 100644 index 0000000000..02fd800baf --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs @@ -0,0 +1,124 @@ +using System.Timers; +using Avalonia.Input.GestureRecognizers; +using Avalonia.Threading; + +namespace Avalonia.Input +{ + public class HoldGestureRecognizer : StyledElement, IGestureRecognizer + { + private const int Tolerance = 30; + private IInputElement? _target; + private IGestureRecognizerActionsDispatcher? _actions; + private int _gestureId; + private IPointer? _tracking; + private PointerPressedEventArgs? _pointerEventArgs; + private Rect _trackingBounds; + private Timer? _holdTimer; + private bool _elasped; + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.Register( + nameof(IsHoldWithMouseEnabled)); + + /// + /// Gets or sets whether to detect hold from the mouse + /// + public bool IsHoldWithMouseEnabled + { + get => GetValue(IsHoldWithMouseEnabledProperty); + set => SetValue(IsHoldWithMouseEnabledProperty, value); + } + + public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) + { + _target = target; + _actions = actions; + + _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); + + _holdTimer = new Timer(300); + _holdTimer.AutoReset = false; + _holdTimer.Elapsed += HoldTimer_Elapsed; + } + + private async void HoldTimer_Elapsed(object? sender, ElapsedEventArgs e) + { + _elasped = true; + _holdTimer?.Stop(); + + if(_tracking != null) + { + await Dispatcher.UIThread.InvokeAsync(() => _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, HoldingState.Started))); + } + } + + private void OnPointerPressed(object? sender, PointerPressedEventArgs e) + { + PointerPressed(e); + } + + private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + public void PointerCaptureLost(IPointer pointer) + { + if (_tracking == pointer) + { + EndHold(!_elasped); + } + } + + public void PointerMoved(PointerEventArgs e) + { + if (_tracking == e.Pointer && _target is Visual visual) + { + var currentPosition = e.GetPosition(visual); + + if (!_trackingBounds.Contains(currentPosition)) + { + EndHold(true); + } + } + } + + public void PointerPressed(PointerPressedEventArgs e) + { + if (_target != null && _target is Visual visual && (IsHoldWithMouseEnabled || e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + { + _elasped = false; + var position = e.GetPosition(visual); + _gestureId = HoldGestureEventArgs.GetNextFreeId(); + _tracking = e.Pointer; + _pointerEventArgs = e; + + _trackingBounds = new Rect(position.X - Tolerance / 2, position.Y - Tolerance / 2, Tolerance, Tolerance); + + _holdTimer?.Start(); + } + } + + public void PointerReleased(PointerReleasedEventArgs e) + { + if (_tracking == e.Pointer) + { + EndHold(!_elasped); + } + } + + private void EndHold(bool cancelled) + { + _holdTimer?.Stop(); + + _tracking = null; + _trackingBounds = default; + + _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, cancelled ? HoldingState.Cancelled : HoldingState.Completed)); + } + } +} diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index b4d5feaf3b..1c2220baf4 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -58,6 +58,10 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent HoldGestureEvent = + RoutedEvent.Register( + "HoldGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent PullGestureEndedEvent = RoutedEvent.Register( "PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); diff --git a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs new file mode 100644 index 0000000000..63a508b1f4 --- /dev/null +++ b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs @@ -0,0 +1,31 @@ +using System; +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class HoldGestureEventArgs : RoutedEventArgs + { + public int Id { get; } + public Vector Delta { get; } + public HoldingState HoldingState { get; } + public PointerEventArgs? PointerEventArgs { get; } + + private static int _nextId = 1; + + internal static int GetNextFreeId() => _nextId++; + + public HoldGestureEventArgs(int id, PointerEventArgs? pointerEventArgs, HoldingState holdingState) : base(Gestures.HoldGestureEvent) + { + Id = id; + HoldingState = holdingState; + PointerEventArgs = pointerEventArgs; + } + } + + public enum HoldingState + { + Started, + Completed, + Cancelled, + } +} diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 88c9823952..de91fb83c9 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -216,6 +216,12 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; + public Control() + { + // Add a default HoldGestureRecognizer + GestureRecognizers.Add(new HoldGestureRecognizer()); + } + /// void ISetterValue.Initialize(ISetter setter) { @@ -366,16 +372,29 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); + AddHandler(Gestures.HoldGestureEvent, OnHoldEvent); + InitializeIfNeeded(); ScheduleOnLoadedCore(); } + private void OnHoldEvent(object? sender, HoldGestureEventArgs e) + { + if(e.HoldingState == HoldingState.Started) + { + // Trigger ContentRequest when hold starts + RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!)); + } + } + /// protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTreeCore(e); + RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent); + OnUnloadedCore(); } From 8945e1b77c78223b4172f419c5d916593f88c62d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 9 Dec 2022 13:48:14 +0000 Subject: [PATCH 095/213] drop hold gesture recognizer --- .../HoldGestureRecognizer.cs | 124 ------------------ src/Avalonia.Base/Input/Gestures.cs | 93 ++++++++++++- .../Input/HoldGestureEventArgs.cs | 31 ----- .../Input/HoldingRoutedEventArgs.cs | 22 ++++ src/Avalonia.Base/Input/InputElement.cs | 46 +++++++ src/Avalonia.Controls/Control.cs | 18 +-- 6 files changed, 163 insertions(+), 171 deletions(-) delete mode 100644 src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs delete mode 100644 src/Avalonia.Base/Input/HoldGestureEventArgs.cs create mode 100644 src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs deleted file mode 100644 index 02fd800baf..0000000000 --- a/src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Timers; -using Avalonia.Input.GestureRecognizers; -using Avalonia.Threading; - -namespace Avalonia.Input -{ - public class HoldGestureRecognizer : StyledElement, IGestureRecognizer - { - private const int Tolerance = 30; - private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; - private int _gestureId; - private IPointer? _tracking; - private PointerPressedEventArgs? _pointerEventArgs; - private Rect _trackingBounds; - private Timer? _holdTimer; - private bool _elasped; - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.Register( - nameof(IsHoldWithMouseEnabled)); - - /// - /// Gets or sets whether to detect hold from the mouse - /// - public bool IsHoldWithMouseEnabled - { - get => GetValue(IsHoldWithMouseEnabledProperty); - set => SetValue(IsHoldWithMouseEnabledProperty, value); - } - - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) - { - _target = target; - _actions = actions; - - _target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - _target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble); - - _holdTimer = new Timer(300); - _holdTimer.AutoReset = false; - _holdTimer.Elapsed += HoldTimer_Elapsed; - } - - private async void HoldTimer_Elapsed(object? sender, ElapsedEventArgs e) - { - _elasped = true; - _holdTimer?.Stop(); - - if(_tracking != null) - { - await Dispatcher.UIThread.InvokeAsync(() => _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, HoldingState.Started))); - } - } - - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) - { - PointerPressed(e); - } - - private void OnPointerReleased(object? sender, PointerReleasedEventArgs e) - { - PointerReleased(e); - } - - public void PointerCaptureLost(IPointer pointer) - { - if (_tracking == pointer) - { - EndHold(!_elasped); - } - } - - public void PointerMoved(PointerEventArgs e) - { - if (_tracking == e.Pointer && _target is Visual visual) - { - var currentPosition = e.GetPosition(visual); - - if (!_trackingBounds.Contains(currentPosition)) - { - EndHold(true); - } - } - } - - public void PointerPressed(PointerPressedEventArgs e) - { - if (_target != null && _target is Visual visual && (IsHoldWithMouseEnabled || e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) - { - _elasped = false; - var position = e.GetPosition(visual); - _gestureId = HoldGestureEventArgs.GetNextFreeId(); - _tracking = e.Pointer; - _pointerEventArgs = e; - - _trackingBounds = new Rect(position.X - Tolerance / 2, position.Y - Tolerance / 2, Tolerance, Tolerance); - - _holdTimer?.Start(); - } - } - - public void PointerReleased(PointerReleasedEventArgs e) - { - if (_tracking == e.Pointer) - { - EndHold(!_elasped); - } - } - - private void EndHold(bool cancelled) - { - _holdTimer?.Stop(); - - _tracking = null; - _trackingBounds = default; - - _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, cancelled ? HoldingState.Cancelled : HoldingState.Completed)); - } - } -} diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 1c2220baf4..06d90f1c27 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -1,6 +1,8 @@ using System; +using System.Threading; using Avalonia.Interactivity; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Input @@ -8,6 +10,16 @@ namespace Avalonia.Input public static class Gestures { private static bool s_isDoubleTapped = false; + private static bool s_isHolding; + private static CancellationTokenSource? s_holdCancellationToken; + + /* /// + /// Defines the property. + /// + public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.RegisterAttached( + "IsHoldWithMouseEnabled");*/ + public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -45,6 +57,7 @@ namespace Avalonia.Input private static readonly WeakReference s_lastPress = new WeakReference(null); private static Point s_lastPressPoint; + private static IPointer? s_lastPointer; public static readonly RoutedEvent PinchEvent = RoutedEvent.Register( @@ -58,9 +71,9 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); - public static readonly RoutedEvent HoldGestureEvent = - RoutedEvent.Register( - "HoldGesture", RoutingStrategies.Bubble, typeof(Gestures)); + public static readonly RoutedEvent HoldingEvent = + RoutedEvent.Register( + "Holding", RoutingStrategies.Bubble, typeof(Gestures)); public static readonly RoutedEvent PullGestureEndedEvent = RoutedEvent.Register( @@ -70,6 +83,7 @@ namespace Avalonia.Input { InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed); InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased); + InputElement.PointerMovedEvent.RouteFinished.Subscribe(PointerMoved); } public static void AddTappedHandler(Interactive element, EventHandler handler) @@ -114,11 +128,38 @@ namespace Avalonia.Input var e = (PointerPressedEventArgs)ev; var visual = (Visual)ev.Source; + if(s_lastPointer != null) + { + if(s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + } + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + + s_lastPointer = null; + } + + s_isHolding = false; + if (e.ClickCount % 2 == 1) { s_isDoubleTapped = false; s_lastPress.SetTarget(ev.Source); + s_lastPointer = e.Pointer; s_lastPressPoint = e.GetPosition((Visual)ev.Source); + s_holdCancellationToken = new CancellationTokenSource(); + var token = s_holdCancellationToken.Token; + var settings = AvaloniaLocator.Current.GetService(); + DispatcherTimer.RunOnce(() => + { + if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + { + s_isHolding = true; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started)); + } + }, TimeSpan.FromMilliseconds(300)); } else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { @@ -152,7 +193,12 @@ namespace Avalonia.Input if (tapRect.ContainsExclusive(point.Position)) { - if (e.InitialPressMouseButton == MouseButton.Right) + if(s_isHolding) + { + s_isHolding = false; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed)); + } + else if (e.InitialPressMouseButton == MouseButton.Right) { i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); } @@ -164,6 +210,45 @@ namespace Avalonia.Input } } } + + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + s_lastPointer = null; + } + } + + private static void PointerMoved(RoutedEventArgs ev) + { + if (ev.Route == RoutingStrategies.Bubble) + { + var e = (PointerEventArgs)ev; + if (s_lastPress.TryGetTarget(out var target)) + { + if (e.Pointer == s_lastPointer) + { + var point = e.GetCurrentPoint((Visual)target); + var settings = AvaloniaLocator.Current.GetService(); + var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4); + var tapRect = new Rect(s_lastPressPoint, new Size()) + .Inflate(new Thickness(tapSize.Width, tapSize.Height)); + + if (tapRect.ContainsExclusive(point.Position)) + { + return; + } + } + + if (s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + } + } + + s_holdCancellationToken?.Cancel(); + s_holdCancellationToken?.Dispose(); + s_holdCancellationToken = null; + s_isHolding = false; } } } diff --git a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs b/src/Avalonia.Base/Input/HoldGestureEventArgs.cs deleted file mode 100644 index 63a508b1f4..0000000000 --- a/src/Avalonia.Base/Input/HoldGestureEventArgs.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using Avalonia.Interactivity; - -namespace Avalonia.Input -{ - public class HoldGestureEventArgs : RoutedEventArgs - { - public int Id { get; } - public Vector Delta { get; } - public HoldingState HoldingState { get; } - public PointerEventArgs? PointerEventArgs { get; } - - private static int _nextId = 1; - - internal static int GetNextFreeId() => _nextId++; - - public HoldGestureEventArgs(int id, PointerEventArgs? pointerEventArgs, HoldingState holdingState) : base(Gestures.HoldGestureEvent) - { - Id = id; - HoldingState = holdingState; - PointerEventArgs = pointerEventArgs; - } - } - - public enum HoldingState - { - Started, - Completed, - Cancelled, - } -} diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs new file mode 100644 index 0000000000..5826c5b994 --- /dev/null +++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs @@ -0,0 +1,22 @@ +using System; +using Avalonia.Interactivity; + +namespace Avalonia.Input +{ + public class HoldingRoutedEventArgs : RoutedEventArgs + { + public HoldingState HoldingState { get; } + + public HoldingRoutedEventArgs(HoldingState holdingState) : base(Gestures.HoldingEvent) + { + HoldingState = holdingState; + } + } + + public enum HoldingState + { + Started, + Completed, + Cancelled, + } +} diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index fa755277cc..7f2cbec299 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -45,6 +45,18 @@ namespace Avalonia.Input public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldingEnabledProperty = + AvaloniaProperty.Register(nameof(IsHoldingEnabled), true); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.Register(nameof(IsHoldWithMouseEnabled), false); + /// /// Defines the property. /// @@ -188,6 +200,11 @@ namespace Avalonia.Input /// public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent; + /// + /// Defines the event. + /// + public static readonly RoutedEvent HoldingEvent = Gestures.HoldingEvent; + /// /// Defines the event. /// @@ -352,6 +369,15 @@ namespace Avalonia.Input add { AddHandler(TappedEvent, value); } remove { RemoveHandler(TappedEvent, value); } } + + /// + /// Occurs when a hold gesture occurs on the control. + /// + public event EventHandler? Holding + { + add { AddHandler(HoldingEvent, value); } + remove { RemoveHandler(HoldingEvent, value); } + } /// /// Occurs when a double-tap gesture occurs on the control. @@ -388,6 +414,26 @@ namespace Avalonia.Input get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } + + /// + /// Gets or sets a value that determines whether the Holding event can originate + /// from that element. + /// + public bool IsHoldingEnabled + { + get { return GetValue(IsHoldingEnabledProperty); } + set { SetValue(IsHoldingEnabledProperty, value); } + } + + /// + /// Gets or sets a value that determines whether the Holding event can originate + /// from that element. + /// + public bool IsHoldWithMouseEnabled + { + get { return GetValue(IsHoldWithMouseEnabledProperty); } + set { SetValue(IsHoldWithMouseEnabledProperty, value); } + } /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index de91fb83c9..bb83f9e8bb 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -216,12 +216,6 @@ namespace Avalonia.Controls /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; - public Control() - { - // Add a default HoldGestureRecognizer - GestureRecognizers.Add(new HoldGestureRecognizer()); - } - /// void ISetterValue.Initialize(ISetter setter) { @@ -372,19 +366,19 @@ namespace Avalonia.Controls { base.OnAttachedToVisualTreeCore(e); - AddHandler(Gestures.HoldGestureEvent, OnHoldEvent); + AddHandler(Gestures.HoldingEvent, OnHoldEvent); InitializeIfNeeded(); ScheduleOnLoadedCore(); } - private void OnHoldEvent(object? sender, HoldGestureEventArgs e) + private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e) { - if(e.HoldingState == HoldingState.Started) + if(e.HoldingState == HoldingState.Completed) { - // Trigger ContentRequest when hold starts - RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!)); + // Trigger ContentRequest when hold is complete + RaiseEvent(new ContextRequestedEventArgs()); } } @@ -393,7 +387,7 @@ namespace Avalonia.Controls { base.OnDetachedFromVisualTreeCore(e); - RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent); + RemoveHandler(Gestures.HoldingEvent, OnHoldEvent); OnUnloadedCore(); } From c804fa00cfe323a00ca7fa6f9e1c91f4ae0f064e Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 9 Dec 2022 18:13:32 +0000 Subject: [PATCH 096/213] fix tests --- .../Input/GesturesTests.cs | 11 +++++++ .../Input/TouchDeviceTests.cs | 2 +- .../ButtonTests.cs | 3 ++ .../ComboBoxTests.cs | 4 +++ .../ListBoxTests_Single.cs | 13 +++++---- .../Mixins/PressedMixinTests.cs | 3 ++ .../Primitives/SelectingItemsControlTests.cs | 8 ++--- .../SelectingItemsControlTests_Multiple.cs | 29 ++++++++++--------- .../TreeViewTests.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 3 +- 10 files changed, 52 insertions(+), 27 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 59085a21ce..20d121756e 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -3,7 +3,9 @@ using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; +using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -15,6 +17,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Follow_Pointer_Pressed_Released() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -32,6 +35,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -49,6 +53,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Middle_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -66,6 +71,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -83,6 +89,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void RightTapped_Should_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -100,6 +107,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -118,6 +126,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -136,6 +145,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -154,6 +164,7 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index c0c0182622..36587ea222 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager())); + new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 8bd51ec500..9cb83d3009 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -134,6 +134,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Raises_Click() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(50, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -166,6 +167,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -199,6 +201,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_With_RenderTransform_Raises_Click() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(150, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 1a8061bfd9..0afcd9ed49 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -11,6 +11,8 @@ using Avalonia.Media; using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; +using Moq; +using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { @@ -21,6 +23,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_Toggles_IsDropDownOpen() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, @@ -41,6 +44,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_PseudoClass() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 03aac5e2f4..7f9b38018b 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -8,6 +8,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -115,7 +116,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -153,7 +154,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -173,7 +174,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -195,7 +196,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -216,7 +217,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs index 0ff1f40121..74d46b18de 100644 --- a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs @@ -1,5 +1,7 @@ using Avalonia.Controls.Mixins; +using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Mixins @@ -19,6 +21,7 @@ namespace Avalonia.Controls.UnitTests.Mixins [Fact] public void Setting_IsSelected_Should_Add_Selected_Class() { + using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new TestControl(); _mouse.Down(target); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index e20c03f67f..c416a5de47 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1118,7 +1118,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1140,7 +1140,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); @@ -1239,7 +1239,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1257,7 +1257,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index bc6147d64c..7fa3839ed5 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -12,6 +12,7 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; +using Avalonia.Platform; using Avalonia.UnitTests; using Moq; using Xunit; @@ -703,7 +704,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -726,7 +727,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -780,7 +781,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -810,7 +811,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -838,7 +839,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -862,7 +863,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -886,7 +887,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -910,7 +911,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -959,7 +960,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1189,7 +1190,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1216,7 +1217,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1241,7 +1242,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1293,7 +1294,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { @@ -1316,7 +1317,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 81936711ef..3e1e25faf5 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -15,6 +15,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -1466,7 +1467,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start()) + using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) { var tree = CreateTestTreeData(); var target = new TreeView diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index 8f132433ec..a2096997c0 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -51,7 +51,8 @@ namespace Avalonia.UnitTests inputManager: new InputManager(), assetLoader: new AssetLoader(), renderInterface: new MockPlatformRenderInterface(), - fontManagerImpl: new MockFontManagerImpl(), + fontManagerImpl: new MockFontManagerImpl(), + threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), textShaperImpl: new MockTextShaperImpl()); public static readonly TestServices TextServices = new TestServices( From 6791a2bca129ae179da39dad3f58c6b41818dbbd Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 12 Dec 2022 09:08:41 +0000 Subject: [PATCH 097/213] Add pointer properties to hold event args, fixup some docs. --- src/Avalonia.Base/Input/Gestures.cs | 18 ++++------ .../Input/HoldingRoutedEventArgs.cs | 33 +++++++++++++++++-- src/Avalonia.Base/Input/InputElement.cs | 3 +- src/Avalonia.Controls/Control.cs | 4 +-- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 06d90f1c27..b1fda35c28 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -13,13 +13,6 @@ namespace Avalonia.Input private static bool s_isHolding; private static CancellationTokenSource? s_holdCancellationToken; - /* /// - /// Defines the property. - /// - public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.RegisterAttached( - "IsHoldWithMouseEnabled");*/ - public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -71,6 +64,9 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGesture", RoutingStrategies.Bubble, typeof(Gestures)); + /// + /// Occurs when a user performs a press and hold gesture (with a single touch, mouse, or pen/stylus contact). + /// public static readonly RoutedEvent HoldingEvent = RoutedEvent.Register( "Holding", RoutingStrategies.Bubble, typeof(Gestures)); @@ -132,7 +128,7 @@ namespace Avalonia.Input { if(s_isHolding && ev.Source is Interactive i) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer.Type)); } s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Dispose(); @@ -157,7 +153,7 @@ namespace Avalonia.Input if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) { s_isHolding = true; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); } }, TimeSpan.FromMilliseconds(300)); } @@ -196,7 +192,7 @@ namespace Avalonia.Input if(s_isHolding) { s_isHolding = false; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastPointer!.Type)); } else if (e.InitialPressMouseButton == MouseButton.Right) { @@ -241,7 +237,7 @@ namespace Avalonia.Input if (s_isHolding && ev.Source is Interactive i) { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled)); + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); } } diff --git a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs index 5826c5b994..b9a877b2ed 100644 --- a/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs +++ b/src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs @@ -5,18 +5,47 @@ namespace Avalonia.Input { public class HoldingRoutedEventArgs : RoutedEventArgs { + /// + /// Gets the state of the event. + /// public HoldingState HoldingState { get; } - - public HoldingRoutedEventArgs(HoldingState holdingState) : base(Gestures.HoldingEvent) + + /// + /// Gets the location of the touch, mouse, or pen/stylus contact. + /// + public Point Position { get; } + + /// + /// Gets the pointer type of the input source. + /// + public PointerType PointerType { get; } + + /// + /// Initializes a new instance of the class. + /// + public HoldingRoutedEventArgs(HoldingState holdingState, Point position, PointerType pointerType) : base(Gestures.HoldingEvent) { HoldingState = holdingState; + Position = position; + PointerType = pointerType; } } public enum HoldingState { + /// + /// A single contact has been detected and a time threshold is crossed without the contact being lifted, another contact detected, or another gesture started. + /// Started, + + /// + /// The single contact is lifted. + /// Completed, + + /// + /// An additional contact is detected or a subsequent gesture (such as a slide) is detected. + /// Cancelled, } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 7f2cbec299..4a65134552 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -426,8 +426,7 @@ namespace Avalonia.Input } /// - /// Gets or sets a value that determines whether the Holding event can originate - /// from that element. + /// Enables or disables support for the press and hold gesture through the left button on a mouse. /// public bool IsHoldWithMouseEnabled { diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index bb83f9e8bb..26db431a0e 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -375,9 +375,9 @@ namespace Avalonia.Controls private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e) { - if(e.HoldingState == HoldingState.Completed) + if(e.HoldingState == HoldingState.Started) { - // Trigger ContentRequest when hold is complete + // Trigger ContentRequest when hold has started RaiseEvent(new ContextRequestedEventArgs()); } } From 8516308e6335b63ac81d7ae57b966fe54c12ede5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:02:20 +0000 Subject: [PATCH 098/213] add HoldWaitDuration to IPlatformSettings --- src/Avalonia.Base/Input/Gestures.cs | 16 ++++++++++------ .../Platform/DefaultPlatformSettings.cs | 2 ++ src/Avalonia.Base/Platform/IPlatformSettings.cs | 2 ++ src/Windows/Avalonia.Win32/Win32Platform.cs | 2 ++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index b1fda35c28..a924b391ac 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -148,14 +148,18 @@ namespace Avalonia.Input s_holdCancellationToken = new CancellationTokenSource(); var token = s_holdCancellationToken.Token; var settings = AvaloniaLocator.Current.GetService(); - DispatcherTimer.RunOnce(() => + + if (settings != null) { - if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + DispatcherTimer.RunOnce(() => { - s_isHolding = true; - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); - } - }, TimeSpan.FromMilliseconds(300)); + if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && (e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + { + s_isHolding = true; + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); + } + }, settings.HoldWaitDuration); + } } else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs index dd3e1a4cb1..d0b27b057f 100644 --- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs @@ -26,5 +26,7 @@ namespace Avalonia.Platform }; } public TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); + + public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); } } diff --git a/src/Avalonia.Base/Platform/IPlatformSettings.cs b/src/Avalonia.Base/Platform/IPlatformSettings.cs index e7921883fd..7c4c1420eb 100644 --- a/src/Avalonia.Base/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Base/Platform/IPlatformSettings.cs @@ -27,5 +27,7 @@ namespace Avalonia.Platform /// tap gesture. /// TimeSpan GetDoubleTapTime(PointerType type); + + TimeSpan HoldWaitDuration { get; set; } } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 93d16b5768..585a9cdf19 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -258,6 +258,8 @@ namespace Avalonia.Win32 public bool CurrentThreadIsLoopThread => _uiThread == Thread.CurrentThread; + public TimeSpan HoldWaitDuration { get; set; } = TimeSpan.FromMilliseconds(300); + public event Action Signaled; public event EventHandler ShutdownRequested; From f61c7f6cd949388e10faa0dc93818147e7ad4c3b Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:03:56 +0000 Subject: [PATCH 099/213] Revert "fix tests" This reverts commit 584e18f78d29480890711b1149d946f61e0d8447. --- .../Input/GesturesTests.cs | 10 ------- .../Input/TouchDeviceTests.cs | 2 +- .../ButtonTests.cs | 3 -- .../ComboBoxTests.cs | 4 --- .../ListBoxTests_Single.cs | 13 ++++----- .../Mixins/PressedMixinTests.cs | 3 -- .../Primitives/SelectingItemsControlTests.cs | 8 ++--- .../SelectingItemsControlTests_Multiple.cs | 29 +++++++++---------- .../TreeViewTests.cs | 3 +- tests/Avalonia.UnitTests/TestServices.cs | 3 +- 10 files changed, 27 insertions(+), 51 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 20d121756e..fedcdcfb1e 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -5,7 +5,6 @@ using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; -using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -17,7 +16,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Follow_Pointer_Pressed_Released() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -35,7 +33,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -53,7 +50,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Middle_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -71,7 +67,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void Tapped_Should_Not_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -89,7 +84,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void RightTapped_Should_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -107,7 +101,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -126,7 +119,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -145,7 +137,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { @@ -164,7 +155,6 @@ namespace Avalonia.Base.UnitTests.Input [Fact] public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); Border border = new Border(); var decorator = new Decorator { diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index 36587ea222..c0c0182622 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); + new TestServices(inputManager: new InputManager())); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 9cb83d3009..8bd51ec500 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -134,7 +134,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Raises_Click() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(50, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -167,7 +166,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) @@ -201,7 +199,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Button_With_RenderTransform_Raises_Click() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var renderer = Mock.Of(); var pt = new Point(150, 50); Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny(), It.IsAny(), It.IsAny>())) diff --git a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs index 0afcd9ed49..1a8061bfd9 100644 --- a/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs @@ -11,8 +11,6 @@ using Avalonia.Media; using Avalonia.VisualTree; using Avalonia.UnitTests; using Xunit; -using Moq; -using Avalonia.Platform; namespace Avalonia.Controls.UnitTests { @@ -23,7 +21,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_Toggles_IsDropDownOpen() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, @@ -44,7 +41,6 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_On_Control_PseudoClass() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new ComboBox { Items = new[] { "Foo", "Bar" }, diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index 7f9b38018b..03aac5e2f4 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -8,7 +8,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.LogicalTree; -using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.VisualTree; @@ -116,7 +115,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -134,7 +133,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -154,7 +153,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -174,7 +173,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Deselect_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -196,7 +195,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Selected_Item_Should_Not_Deselect_It_When_SelectionMode_ToggleAlwaysSelected() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -217,7 +216,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clicking_Another_Item_Should_Select_It_When_SelectionMode_Toggle() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs index 74d46b18de..0ff1f40121 100644 --- a/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Mixins/PressedMixinTests.cs @@ -1,7 +1,5 @@ using Avalonia.Controls.Mixins; -using Avalonia.Platform; using Avalonia.UnitTests; -using Moq; using Xunit; namespace Avalonia.Controls.UnitTests.Mixins @@ -21,7 +19,6 @@ namespace Avalonia.Controls.UnitTests.Mixins [Fact] public void Setting_IsSelected_Should_Add_Selected_Class() { - using var app = UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var target = new TestControl(); _mouse.Down(target); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index c416a5de47..e20c03f67f 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1118,7 +1118,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Setting_SelectedItem_With_Pointer_Should_Set_TabOnceActiveElement() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1140,7 +1140,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Removing_SelectedItem_Should_Clear_TabOnceActiveElement() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var items = new ObservableCollection(new[] { "Foo", "Bar", "Baz " }); @@ -1239,7 +1239,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1257,7 +1257,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Apply_Selected_Pseudoclass_To_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 7fa3839ed5..bc6147d64c 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -12,7 +12,6 @@ using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; -using Avalonia.Platform; using Avalonia.UnitTests; using Moq; using Xunit; @@ -704,7 +703,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_From_No_Selection_Selects_From_Start() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -727,7 +726,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -781,7 +780,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_SelectedItem_With_Multiple_Selection_Active_Sets_SelectedItem_To_Next_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -811,7 +810,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Selecting_Non_SelectedItem_With_Multiple_Selection_Active_Leaves_SelectedItem_The_Same() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -839,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Ctrl_Select_Correct_Item_When_Duplicate_Items_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -863,7 +862,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Should_Shift_Select_Correct_Item_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -887,7 +886,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Can_Shift_Select_All_Items_When_Duplicates_Are_Present() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -911,7 +910,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Selecting_Raises_SelectionChanged_Events() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -960,7 +959,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Duplicate_Items_Are_Added_To_SelectedItems_In_Order() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1190,7 +1189,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Left_Click_On_SelectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1217,7 +1216,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1242,7 +1241,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Right_Click_On_UnselectedItem_Should_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1294,7 +1293,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Shift_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { @@ -1317,7 +1316,7 @@ namespace Avalonia.Controls.UnitTests.Primitives [Fact] public void Ctrl_Right_Click_Should_Not_Select_Multiple() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var target = new ListBox { diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 3e1e25faf5..81936711ef 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -15,7 +15,6 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Platform; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -1467,7 +1466,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Right_Click_On_SelectedItem_Should_Not_Clear_Existing_Selection() { - using (UnitTestApplication.Start(new TestServices(threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true)))) + using (UnitTestApplication.Start()) { var tree = CreateTestTreeData(); var target = new TreeView diff --git a/tests/Avalonia.UnitTests/TestServices.cs b/tests/Avalonia.UnitTests/TestServices.cs index a2096997c0..8f132433ec 100644 --- a/tests/Avalonia.UnitTests/TestServices.cs +++ b/tests/Avalonia.UnitTests/TestServices.cs @@ -51,8 +51,7 @@ namespace Avalonia.UnitTests inputManager: new InputManager(), assetLoader: new AssetLoader(), renderInterface: new MockPlatformRenderInterface(), - fontManagerImpl: new MockFontManagerImpl(), - threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true), + fontManagerImpl: new MockFontManagerImpl(), textShaperImpl: new MockTextShaperImpl()); public static readonly TestServices TextServices = new TestServices( From be843f699a677154d7acca6ce9eba4b287efda42 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 14:19:01 +0000 Subject: [PATCH 100/213] add mock thread interface to touch device tests --- tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index c0c0182622..36587ea222 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -207,7 +207,7 @@ namespace Avalonia.Input.UnitTests private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) { var unitTestApp = UnitTestApplication.Start( - new TestServices(inputManager: new InputManager())); + new TestServices(inputManager: new InputManager(), threadingInterface: Mock.Of(x => x.CurrentThreadIsLoopThread == true))); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); From 29e7666a679e7994ba6d857543bb135753b494b1 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 22 Dec 2022 16:22:27 +0000 Subject: [PATCH 101/213] attempt at testing --- .../Input/GesturesTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index fedcdcfb1e..46df719d69 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,10 +1,13 @@ +using System; using System.Collections.Generic; +using System.Threading; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; +using Moq; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -170,6 +173,35 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(raised); } + [Fact] + public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + { + using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface); + + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = true); + + var secondMouse = new MouseTestHelper(); + + _mouse.Down(border, MouseButton.Left); + + Thread.Sleep(1000); + secondMouse.Down(border, MouseButton.Left); + + Assert.False(raised); + } + private static void AddHandlers( Decorator decorator, Border border, From 17765223976844e9ebb1908497bc8edc3d73506a Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 22 Dec 2022 11:59:33 -0500 Subject: [PATCH 102/213] Mock IPlatformThreadingInterface --- .../Input/GesturesTests.cs | 100 +++++++++++++++++- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 46df719d69..82aa3eab72 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -6,6 +6,7 @@ using Avalonia.Input; using Avalonia.Input.GestureRecognizers; using Avalonia.Media; using Avalonia.Platform; +using Avalonia.Threading; using Avalonia.UnitTests; using Moq; using Xunit; @@ -174,14 +175,52 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + public void Hold_Is_Raised_When_Pointer_Pressed() { - using var app = UnitTestApplication.Start(TestServices.MockThreadingInterface); + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Started); + + _mouse.Down(border); + Assert.False(raised); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + Assert.True(raised); + } + + [Fact] + public void Hold_Is_Not_Raised_When_Pointer_Released_Before_Timer() + { + using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(iSettingsMock.Object); + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + Border border = new Border(); border.IsHoldWithMouseEnabled = true; var decorator = new Decorator @@ -190,18 +229,69 @@ namespace Avalonia.Base.UnitTests.Input }; var raised = false; - decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = true); + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed); + + _mouse.Down(border); + Assert.False(raised); + + _mouse.Up(border); + Assert.False(raised); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + Assert.False(raised); + } + + [Fact] + public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var raised = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => raised = e.HoldingState == HoldingState.Completed); var secondMouse = new MouseTestHelper(); _mouse.Down(border, MouseButton.Left); - Thread.Sleep(1000); + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + secondMouse.Down(border, MouseButton.Left); Assert.False(raised); } + private static IPlatformThreadingInterface CreatePlatformThreadingInterface(Action<(TimeSpan, Action)> callback) + { + var threadingInterface = new Mock(); + threadingInterface.SetupGet(p => p.CurrentThreadIsLoopThread).Returns(true); + threadingInterface.Setup(p => p + .StartTimer(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((_, t, a) => callback((t, a))); + return threadingInterface.Object; + } + private static void AddHandlers( Decorator decorator, Border border, From 08f17fa2068f09c36f1730c359eb97e717c4dfa7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 23 Dec 2022 09:22:18 +0000 Subject: [PATCH 103/213] add more tests --- .../Input/GesturesTests.cs | 73 ++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 82aa3eab72..33a01fc9cd 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Threading; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.GestureRecognizers; @@ -244,6 +243,78 @@ namespace Avalonia.Base.UnitTests.Input Assert.False(raised); } + + [Fact] + public void Hold_Is_Cancelled_When_Second_Contact_Is_Detected() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var cancelled = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + + _mouse.Down(border); + Assert.False(cancelled); + + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + var secondMouse = new MouseTestHelper(); + + secondMouse.Down(border); + + Assert.True(cancelled); + } + + [Fact] + public void Hold_Is_Cancelled_When_Pointer_Moves() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + var cancelled = false; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); + + _mouse.Down(border); + Assert.False(cancelled); + + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + _mouse.Move(border, position: new Point(10, 10)); + + Assert.True(cancelled); + } [Fact] public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() From 306712e4cce1f63be61357a5f1c0456374bebf0c Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 23 Dec 2022 12:33:28 +0000 Subject: [PATCH 104/213] make IsHoldingEnabledProperty and IsHoldWithMouseEnabledProperty attached properties in Gestures --- src/Avalonia.Base/Input/Gestures.cs | 30 +++++++++++++++++++++++++ src/Avalonia.Base/Input/InputElement.cs | 26 ++++++++++----------- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index a924b391ac..a91e37177a 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -13,6 +13,18 @@ namespace Avalonia.Input private static bool s_isHolding; private static CancellationTokenSource? s_holdCancellationToken; + /// + /// Defines the IsHoldingEnabled attached property. + /// + public static readonly AttachedProperty IsHoldingEnabledProperty = + AvaloniaProperty.RegisterAttached("IsHoldingEnabled", typeof(Gestures), true); + + /// + /// Defines the IsHoldWithMouseEnabled attached property. + /// + public static readonly AttachedProperty IsHoldWithMouseEnabledProperty = + AvaloniaProperty.RegisterAttached("IsHoldWithMouseEnabled", typeof(Gestures), false); + public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -75,6 +87,24 @@ namespace Avalonia.Input RoutedEvent.Register( "PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); + public static bool GetIsHoldingEnabled(StyledElement element) + { + return element.GetValue(IsHoldingEnabledProperty); + } + public static void SetIsHoldingEnabled(StyledElement element, bool value) + { + element.SetValue(IsHoldingEnabledProperty, value); + } + + public static bool GetIsHoldWithMouseEnabled(StyledElement element) + { + return element.GetValue(IsHoldWithMouseEnabledProperty); + } + public static void SetIsHoldWithMouseEnabled(StyledElement element, bool value) + { + element.SetValue(IsHoldWithMouseEnabledProperty, value); + } + static Gestures() { InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed); diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 4a65134552..e1835241bf 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -45,18 +45,6 @@ namespace Avalonia.Input public static readonly StyledProperty CursorProperty = AvaloniaProperty.Register(nameof(Cursor), null, true); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldingEnabledProperty = - AvaloniaProperty.Register(nameof(IsHoldingEnabled), true); - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - AvaloniaProperty.Register(nameof(IsHoldWithMouseEnabled), false); - /// /// Defines the property. /// @@ -209,7 +197,19 @@ namespace Avalonia.Input /// Defines the event. /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; - + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldingEnabledProperty = + Gestures.IsHoldingEnabledProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsHoldWithMouseEnabledProperty = + Gestures.IsHoldWithMouseEnabledProperty.AddOwner(); + private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isKeyboardFocusWithin; From 57255586c27a1e1296f3b01f18bc3e9598d2ac69 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 27 Dec 2022 12:50:25 +0000 Subject: [PATCH 105/213] update hold tests --- src/Avalonia.Base/Input/Gestures.cs | 8 +-- .../Input/GesturesTests.cs | 68 ++++++++++++++++--- 2 files changed, 61 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index a91e37177a..9708ca2b48 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -267,11 +267,11 @@ namespace Avalonia.Input { return; } - } - if (s_isHolding && ev.Source is Interactive i) - { - i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); + if (s_isHolding && ev.Source is Interactive i) + { + i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); + } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 33a01fc9cd..1b0c941082 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -174,7 +174,46 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Raised_When_Pointer_Pressed() + public void Hold_Should_Be_Raised_After_Hold_Duration() + { + using var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + + var scheduledTimers = new List<(TimeSpan time, Action action)>(); + using var app = UnitTestApplication.Start(new TestServices( + threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); + + Border border = new Border(); + border.IsHoldWithMouseEnabled = true; + var decorator = new Decorator + { + Child = border + }; + HoldingState holding = HoldingState.Cancelled; + + decorator.AddHandler(Gestures.HoldingEvent, (s, e) => holding = e.HoldingState); + + _mouse.Down(border); + Assert.False(holding != HoldingState.Cancelled); + + // Verify timer duration, but execute it immediately. + var timer = Assert.Single(scheduledTimers); + Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); + timer.action(); + + Assert.True(holding == HoldingState.Started); + + _mouse.Up(border); + + Assert.True(holding == HoldingState.Completed); + } + + [Fact] + public void Hold_Should_Not_Raised_When_Pointer_Released_Before_Timer() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -199,16 +238,19 @@ namespace Avalonia.Base.UnitTests.Input _mouse.Down(border); Assert.False(raised); + _mouse.Up(border); + Assert.False(raised); + // Verify timer duration, but execute it immediately. var timer = Assert.Single(scheduledTimers); Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); timer.action(); - Assert.True(raised); + Assert.False(raised); } [Fact] - public void Hold_Is_Not_Raised_When_Pointer_Released_Before_Timer() + public void Hold_Should_Not_Raised_When_Pointer_Is_Moved_Before_Timer() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -232,8 +274,8 @@ namespace Avalonia.Base.UnitTests.Input _mouse.Down(border); Assert.False(raised); - - _mouse.Up(border); + + _mouse.Move(border, position: new Point(20, 20)); Assert.False(raised); // Verify timer duration, but execute it immediately. @@ -245,7 +287,7 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Cancelled_When_Second_Contact_Is_Detected() + public void Hold_Should_Be_Cancelled_When_Second_Contact_Is_Detected() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); @@ -282,11 +324,12 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Hold_Is_Cancelled_When_Pointer_Moves() + public void Hold_Should_Be_Cancelled_When_Pointer_Moves_Too_Far() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.HoldWaitDuration).Returns(TimeSpan.FromMilliseconds(300)); + iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16)); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(iSettingsMock.Object); @@ -305,19 +348,22 @@ namespace Avalonia.Base.UnitTests.Input decorator.AddHandler(Gestures.HoldingEvent, (s, e) => cancelled = e.HoldingState == HoldingState.Cancelled); _mouse.Down(border); - Assert.False(cancelled); var timer = Assert.Single(scheduledTimers); Assert.Equal(iSettingsMock.Object.HoldWaitDuration, timer.time); timer.action(); - _mouse.Move(border, position: new Point(10, 10)); + _mouse.Move(border, position: new Point(3, 3)); + + Assert.False(cancelled); + + _mouse.Move(border, position: new Point(20, 20)); Assert.True(cancelled); } - + [Fact] - public void Hold_Should_Not_Be_Raised_For_Multiple_Contact() + public void Hold_Should_Not_Be_Raised_For_Multiple_Contacts() { using var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); From 66ca4ca6399f964c30e09672aca42fb7d016b106 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 28 Dec 2022 08:55:21 +0000 Subject: [PATCH 106/213] remove hold styled properties in InputElement --- src/Avalonia.Base/Input/Gestures.cs | 2 +- src/Avalonia.Base/Input/InputElement.cs | 31 ------------------- .../Input/GesturesTests.cs | 12 +++---- 3 files changed, 7 insertions(+), 38 deletions(-) diff --git a/src/Avalonia.Base/Input/Gestures.cs b/src/Avalonia.Base/Input/Gestures.cs index 9708ca2b48..54e61d89b2 100644 --- a/src/Avalonia.Base/Input/Gestures.cs +++ b/src/Avalonia.Base/Input/Gestures.cs @@ -183,7 +183,7 @@ namespace Avalonia.Input { DispatcherTimer.RunOnce(() => { - if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && (e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled)) + if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i))) { s_isHolding = true; i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastPointer.Type)); diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index e1835241bf..f233fdce51 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -198,18 +198,6 @@ namespace Avalonia.Input /// public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldingEnabledProperty = - Gestures.IsHoldingEnabledProperty.AddOwner(); - - /// - /// Defines the property. - /// - public static readonly StyledProperty IsHoldWithMouseEnabledProperty = - Gestures.IsHoldWithMouseEnabledProperty.AddOwner(); - private bool _isEffectivelyEnabled = true; private bool _isFocused; private bool _isKeyboardFocusWithin; @@ -414,25 +402,6 @@ namespace Avalonia.Input get { return GetValue(CursorProperty); } set { SetValue(CursorProperty, value); } } - - /// - /// Gets or sets a value that determines whether the Holding event can originate - /// from that element. - /// - public bool IsHoldingEnabled - { - get { return GetValue(IsHoldingEnabledProperty); } - set { SetValue(IsHoldingEnabledProperty, value); } - } - - /// - /// Enables or disables support for the press and hold gesture through the left button on a mouse. - /// - public bool IsHoldWithMouseEnabled - { - get { return GetValue(IsHoldWithMouseEnabledProperty); } - set { SetValue(IsHoldWithMouseEnabledProperty, value); } - } /// /// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements. diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 1b0c941082..84ee35ba61 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -188,7 +188,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -226,7 +226,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -263,7 +263,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -300,7 +300,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -338,7 +338,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border @@ -376,7 +376,7 @@ namespace Avalonia.Base.UnitTests.Input threadingInterface: CreatePlatformThreadingInterface(t => scheduledTimers.Add(t)))); Border border = new Border(); - border.IsHoldWithMouseEnabled = true; + Gestures.SetIsHoldWithMouseEnabled(border, true); var decorator = new Decorator { Child = border From 4780567438950cd9269cd84cbef36b41d870872f Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 28 Dec 2022 15:15:35 +0200 Subject: [PATCH 107/213] Don't reomve old content from logicaltree --- src/Avalonia.Controls/ContentControl.cs | 11 +++- .../TransitioningContentControl.cs | 7 +++ .../TransitioningContentControlTests.cs | 63 +++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index b8a45e102f..d47a7a7809 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -116,14 +116,19 @@ namespace Avalonia.Controls return false; } - private void ContentChanged(AvaloniaPropertyChangedEventArgs e) + protected virtual void ContentChanged(AvaloniaPropertyChangedEventArgs e) { - if (e.OldValue is ILogical oldChild) + UpdateLogicalTree(e.OldValue, e.NewValue); + } + + protected void UpdateLogicalTree(object? toRemove, object? toAdd) + { + if (toRemove is ILogical oldChild) { LogicalChildren.Remove(oldChild); } - if (e.NewValue is ILogical newChild) + if (toAdd is ILogical newChild) { LogicalChildren.Add(newChild); } diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 70b21b7248..545032befb 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -71,6 +71,11 @@ public class TransitioningContentControl : ContentControl } } + protected override void ContentChanged(AvaloniaPropertyChangedEventArgs e) + { + // We do nothing becuse we should not remove old Content until the animation is over + } + /// /// Updates the content with transitions. /// @@ -89,6 +94,8 @@ public class TransitioningContentControl : ContentControl if (PageTransition != null) await PageTransition.Start(this, null, true, localToken); + UpdateLogicalTree(CurrentContent, content); + if (localToken.IsCancellationRequested) { return; diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs new file mode 100644 index 0000000000..fa523d7f78 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -0,0 +1,63 @@ +using System; +using Avalonia.LogicalTree; +using Avalonia.UnitTests; +using Xunit; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Animation; + +namespace Avalonia.Controls.UnitTests +{ + public class TransitioningContentControlTests + { + [Fact] + public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() + { + var testTransition = new TestTransition(); + + var target = new TransitioningContentControl(); + target.PageTransition = testTransition; + + var root = new TestRoot() { Child = target }; + + var oldControl = new Control(); + var newControl = new Control(); + + target.Content = oldControl; + Threading.Dispatcher.UIThread.RunJobs(); + + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + + testTransition.BeginTransition += isFrom => + { + // Old out + if (isFrom) + { + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + } + // New in + else + { + Assert.Equal(null, oldControl.GetLogicalParent()); + Assert.Equal(target, newControl.GetLogicalParent()); + } + }; + + target.Content = newControl; + Threading.Dispatcher.UIThread.RunJobs(); + } + } + public class TestTransition : IPageTransition + { + public event Action BeginTransition; + + public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) + { + bool isFrom = from != null && to == null; + BeginTransition?.Invoke(isFrom); + return Task.CompletedTask; + } + } +} From 46eca014a2ba5cd4528ac755172c821695a43661 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Wed, 28 Dec 2022 15:46:54 +0100 Subject: [PATCH 108/213] ScrollGestureRecognizer: fix skipping ScrollStartDistance on start of scroll; fix end scrolling too early using InertialScrollSpeedEnd; adding VelocityTracker that uses the last 20 pointermoves for calculating scroll velocity on pointerreleased. --- .../ScrollGestureRecognizer.cs | 43 +- .../GestureRecognizers/VelocityTracker.cs | 424 ++++++++++++++++++ 2 files changed, 454 insertions(+), 13 deletions(-) create mode 100644 src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 64fe275547..e2deea0fde 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -17,7 +17,9 @@ namespace Avalonia.Input.GestureRecognizers private bool _canVerticallyScroll; private int _gestureId; private int _scrollStartDistance = 30; - + private Point _pointerPressedPoint; + private VelocityTracker? _velocityTracker; + // Movement per second private Vector _inertia; private ulong? _lastMoveTimestamp; @@ -91,7 +93,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId(); - _trackedRootPoint = e.GetPosition((Visual?)_target); + _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target); } } @@ -111,6 +113,13 @@ namespace Avalonia.Input.GestureRecognizers _scrolling = true; if (_scrolling) { + _velocityTracker = new VelocityTracker(); // TODO: Should be platform specific -- this default tracker is for Android. + + // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance + _trackedRootPoint = new Point( + _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? _scrollStartDistance : -_scrollStartDistance), + _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? _scrollStartDistance : -_scrollStartDistance)); + _actions!.Capture(e.Pointer, this); } } @@ -118,14 +127,11 @@ namespace Avalonia.Input.GestureRecognizers if (_scrolling) { var vector = _trackedRootPoint - rootPoint; - var elapsed = _lastMoveTimestamp.HasValue && _lastMoveTimestamp < e.Timestamp ? - TimeSpan.FromMilliseconds(e.Timestamp - _lastMoveTimestamp.Value) : - TimeSpan.Zero; - + + _velocityTracker?.AddPosition(TimeSpan.FromMilliseconds(e.Timestamp), _pointerPressedPoint - rootPoint); + _lastMoveTimestamp = e.Timestamp; _trackedRootPoint = rootPoint; - if (elapsed.TotalSeconds > 0) - _inertia = vector / elapsed.TotalSeconds; _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); e.Handled = true; } @@ -150,12 +156,14 @@ namespace Avalonia.Input.GestureRecognizers } } - - + + public void PointerReleased(PointerReleasedEventArgs e) { if (e.Pointer == _tracking && _scrolling) { + _inertia = _velocityTracker?.GetFlingVelocity().PixelsPerSecond ?? Vector.Zero; + e.Handled = true; if (_inertia == default || e.Timestamp == 0 @@ -183,9 +191,18 @@ namespace Avalonia.Input.GestureRecognizers var distance = speed * elapsedSinceLastTick.TotalSeconds; _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); - - - if (Math.Abs(speed.X) < InertialScrollSpeedEnd || Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling + if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + { + EndGesture(); + return false; + } + else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) + { + EndGesture(); + return false; + } + else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd) { EndGesture(); return false; diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs new file mode 100644 index 0000000000..909010d43d --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -0,0 +1,424 @@ +// Code in this file is derived from +// https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart + +//Copyright 2014 The Flutter Authors. All rights reserved. + +//Redistribution and use in source and binary forms, with or without modification, +//are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. + +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; +//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +using System; +using System.Diagnostics; + +namespace Avalonia.Input.GestureRecognizers +{ + // TODO: add 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? + + public readonly record struct Velocity(Vector PixelsPerSecond) + { + public Velocity ClampMagnitude(double minValue, double maxValue) + { + Debug.Assert(minValue >= 0.0); + Debug.Assert(maxValue >= 0.0 && maxValue >= minValue); + double valueSquared = PixelsPerSecond.SquaredLength; + if (valueSquared > maxValue * maxValue) + { + double length = PixelsPerSecond.Length; + return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * maxValue : Vector.Zero); + // preventing double.NaN in Vector PixelsPerSecond is important -- if a NaN eventually gets into a + // ScrollGestureEventArgs it results in runtime errors. + } + if (valueSquared < minValue * minValue) + { + double length = PixelsPerSecond.Length; + return new Velocity(length != 0.0 ? (PixelsPerSecond / length) * minValue : Vector.Zero); + } + return this; + } + } + + /// A two dimensional velocity estimate. + /// + /// VelocityEstimates are computed by [VelocityTracker.getVelocityEstimate]. An + /// estimate's [confidence] measures how well the velocity tracker's position + /// data fit a straight line, [duration] is the time that elapsed between the + /// first and last position sample used to compute the velocity, and [offset] + /// is similarly the difference between the first and last positions. + /// + /// See also: + /// + /// * [VelocityTracker], which computes [VelocityEstimate]s. + /// * [Velocity], which encapsulates (just) a velocity vector and provides some + /// useful velocity operations. + public record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset); + + internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time); + + /// Computes a pointer's velocity based on data from [PointerMoveEvent]s. + /// + /// The input data is provided by calling [addPosition]. Adding data is cheap. + /// + /// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will + /// compute the velocity based on the data added so far. Only call these when + /// you need to use the velocity, as they are comparatively expensive. + /// + /// The quality of the velocity estimation will be better if more data points + /// have been received. + public class VelocityTracker + { + private const int AssumePointerMoveStoppedMilliseconds = 40; + private const int HistorySize = 20; + private const int HorizonMilliseconds = 100; + private const int MinSampleSize = 3; + private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) + private const double MaxFlingVelocity = 8000.0; + + private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; + private int _index = 0; + + /// + /// Adds a position as the given time to the tracker. + /// + /// + /// + public void AddPosition(TimeSpan time, Vector position) + { + _index++; + if (_index == HistorySize) + { + _index = 0; + } + _samples[_index] = new PointAtTime(true, position, time); + } + + /// Returns an estimate of the velocity of the object being tracked by the + /// tracker given the current information available to the tracker. + /// + /// Information is added using [addPosition]. + /// + /// Returns null if there is no data on which to base an estimate. + protected virtual VelocityEstimate? GetVelocityEstimate() + { + double[] x = new double[HistorySize]; + double[] y = new double[HistorySize]; + double[] w = new double[HistorySize]; + double[] time = new double[HistorySize]; + int sampleCount = 0; + int index = _index; + + var newestSample = _samples[index]; + if (!newestSample.Valid) + { + return null; + } + + var previousSample = newestSample; + var oldestSample = newestSample; + + // Starting with the most recent PointAtTime sample, iterate backwards while + // the samples represent continuous motion. + do + { + var sample = _samples[index]; + if (!sample.Valid) + { + break; + } + + double age = (newestSample.Time - sample.Time).TotalMilliseconds; + double delta = Math.Abs((sample.Time - previousSample.Time).TotalMilliseconds); + previousSample = sample; + if (age > HorizonMilliseconds || delta > AssumePointerMoveStoppedMilliseconds) + { + break; + } + + oldestSample = sample; + var position = sample.Point; + x[sampleCount] = position.X; + y[sampleCount] = position.Y; + w[sampleCount] = 1.0; + time[sampleCount] = -age; + index = (index == 0 ? HistorySize : index) - 1; + + sampleCount++; + } while (sampleCount < HistorySize); + + if (sampleCount >= MinSampleSize) + { + var xFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), x.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + if (xFit != null) + { + var yFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), y.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + if (yFit != null) + { + return new VelocityEstimate( // convert from pixels/ms to pixels/s + PixelsPerSecond: new Vector(xFit.Coefficients[1] * 1000, yFit.Coefficients[1] * 1000), + Confidence: xFit.Confidence * yFit.Confidence, + Duration: newestSample.Time - oldestSample.Time, + Offset: newestSample.Point - oldestSample.Point + ); + } + } + } + + // We're unable to make a velocity estimate but we did have at least one + // valid pointer position. + return new VelocityEstimate( + PixelsPerSecond: Vector.Zero, + Confidence: 1.0, + Duration: newestSample.Time - oldestSample.Time, + Offset: newestSample.Point - oldestSample.Point + ); + } + + /// + /// Computes the velocity of the pointer at the time of the last + /// provided data point. + /// + /// This can be expensive. Only call this when you need the velocity. + /// + /// Returns [Velocity.zero] if there is no data from which to compute an + /// estimate or if the estimated velocity is zero./// + /// + /// + public Velocity GetVelocity() + { + var estimate = GetVelocityEstimate(); + if (estimate == null || estimate.PixelsPerSecond.IsDefault) + { + return new Velocity(Vector.Zero); + } + return new Velocity(estimate.PixelsPerSecond); + } + + public virtual Velocity GetFlingVelocity() + { + return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity); + } + } + + /// An nth degree polynomial fit to a dataset. + internal class PolynomialFit + { + /// Creates a polynomial fit of the given degree. + /// + /// There are n + 1 coefficients in a fit of degree n. + internal PolynomialFit(int degree) + { + Coefficients = new double[degree + 1]; + } + + /// The polynomial coefficients of the fit. + public double[] Coefficients { get; } + + /// An indicator of the quality of the fit. + /// + /// Larger values indicate greater quality. + public double Confidence { get; set; } + } + + internal class LeastSquaresSolver + { + private const double PrecisionErrorTolerance = 1e-10; + + /// + /// Fits a polynomial of the given degree to the data points. + /// When there is not enough data to fit a curve null is returned. + /// + public static PolynomialFit? Solve(int degree, ReadOnlySpan x, ReadOnlySpan y, ReadOnlySpan w) + { + if (degree > x.Length) + { + // Not enough data to fit a curve. + return null; + } + + PolynomialFit result = new PolynomialFit(degree); + + // Shorthands for the purpose of notation equivalence to original C++ code. + int m = x.Length; + int n = degree + 1; + + // Expand the X vector to a matrix A, pre-multiplied by the weights. + _Matrix a = new _Matrix(n, m); + for (int h = 0; h < m; h += 1) + { + a.Set(0, h, w[h]); + for (int i = 1; i < n; i += 1) + { + a.Set(i, h, a.Get(i - 1, h) * x[h]); + } + } + + // Apply the Gram-Schmidt process to A to obtain its QR decomposition. + + // Orthonormal basis, column-major ordVectorer. + _Matrix q = new _Matrix(n, m); + // Upper triangular matrix, row-major order. + _Matrix r = new _Matrix(n, n); + for (int j = 0; j < n; j += 1) + { + for (int h = 0; h < m; h += 1) + { + q.Set(j, h, a.Get(j, h)); + } + for (int i = 0; i < j; i += 1) + { + double dot = q.GetRow(j) * q.GetRow(i); + for (int h = 0; h < m; h += 1) + { + q.Set(j, h, q.Get(j, h) - dot * q.Get(i, h)); + } + } + + double norm = q.GetRow(j).Norm(); + if (norm < PrecisionErrorTolerance) + { + // Vectors are linearly dependent or zero so no solution. + return null; + } + + double inverseNorm = 1.0 / norm; + for (int h = 0; h < m; h += 1) + { + q.Set(j, h, q.Get(j, h) * inverseNorm); + } + for (int i = 0; i < n; i += 1) + { + r.Set(j, i, i < j ? 0.0 : q.GetRow(j) * a.GetRow(i)); + } + } + + // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. + // We just work from bottom-right to top-left calculating B's coefficients. + _Vector wy = new _Vector(m); + for (int h = 0; h < m; h += 1) + { + wy[h] = y[h] * w[h]; + } + for (int i = n - 1; i >= 0; i -= 1) + { + result.Coefficients[i] = q.GetRow(i) * wy; + for (int j = n - 1; j > i; j -= 1) + { + result.Coefficients[i] -= r.Get(i, j) * result.Coefficients[j]; + } + result.Coefficients[i] /= r.Get(i, i); + } + + // Calculate the coefficient of determination (confidence) as: + // 1 - (sumSquaredError / sumSquaredTotal) + // ...where sumSquaredError is the residual sum of squares (variance of the + // error), and sumSquaredTotal is the total sum of squares (variance of the + // data) where each has been weighted. + double yMean = 0.0; + for (int h = 0; h < m; h += 1) + { + yMean += y[h]; + } + yMean /= m; + + double sumSquaredError = 0.0; + double sumSquaredTotal = 0.0; + for (int h = 0; h < m; h += 1) + { + double term = 1.0; + double err = y[h] - result.Coefficients[0]; + for (int i = 1; i < n; i += 1) + { + term *= x[h]; + err -= term * result.Coefficients[i]; + } + sumSquaredError += w[h] * w[h] * err * err; + double v = y[h] - yMean; + sumSquaredTotal += w[h] * w[h] * v * v; + } + + result.Confidence = sumSquaredTotal <= PrecisionErrorTolerance ? 1.0 : + 1.0 - (sumSquaredError / sumSquaredTotal); + + return result; + } + + private readonly struct _Vector + { + private readonly int _offset; + private readonly int _length; + private readonly double[] _elements; + + internal _Vector(int size) + { + _offset = 0; + _length = size; + _elements = new double[size]; + } + + internal _Vector(double[] values, int offset, int length) + { + _offset = offset; + _length = length; + _elements = values; + } + + public double this[int i] + { + get => _elements[i + _offset]; + set => _elements[i + _offset] = value; + } + + public static double operator *(_Vector a, _Vector b) + { + double result = 0.0; + for (int i = 0; i < a._length; i += 1) + { + result += a[i] * b[i]; + } + return result; + } + + public double Norm() => Math.Sqrt(this * this); + } + + private readonly struct _Matrix + { + private readonly int _columns; + private readonly double[] _elements; + + internal _Matrix(int rows, int cols) + { + _columns = cols; + _elements = new double[rows * cols]; + } + + public double Get(int row, int col) => _elements[row * _columns + col]; + public void Set(int row, int col, double value) + { + _elements[row * _columns + col] = value; + } + + public _Vector GetRow(int row) => new(_elements, row * _columns, _columns); + } + } +} From 5f5f5ebc9b8dd0ed3a76a64f170325f410ac0cc6 Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 28 Dec 2022 20:07:31 +0200 Subject: [PATCH 109/213] try fix tests --- .../TransitioningContentControlTests.cs | 55 ++++++++++--------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index fa523d7f78..ac944352c2 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -13,40 +13,43 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() { - var testTransition = new TestTransition(); + using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + { + var testTransition = new TestTransition(); - var target = new TransitioningContentControl(); - target.PageTransition = testTransition; + var target = new TransitioningContentControl(); + target.PageTransition = testTransition; - var root = new TestRoot() { Child = target }; + var root = new TestRoot() { Child = target }; - var oldControl = new Control(); - var newControl = new Control(); + var oldControl = new Control(); + var newControl = new Control(); - target.Content = oldControl; - Threading.Dispatcher.UIThread.RunJobs(); + target.Content = oldControl; + Threading.Dispatcher.UIThread.RunJobs(); - Assert.Equal(target, oldControl.GetLogicalParent()); - Assert.Equal(null, newControl.GetLogicalParent()); + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); - testTransition.BeginTransition += isFrom => - { - // Old out - if (isFrom) - { - Assert.Equal(target, oldControl.GetLogicalParent()); - Assert.Equal(null, newControl.GetLogicalParent()); - } - // New in - else + testTransition.BeginTransition += isFrom => { - Assert.Equal(null, oldControl.GetLogicalParent()); - Assert.Equal(target, newControl.GetLogicalParent()); - } - }; + // Old out + if (isFrom) + { + Assert.Equal(target, oldControl.GetLogicalParent()); + Assert.Equal(null, newControl.GetLogicalParent()); + } + // New in + else + { + Assert.Equal(null, oldControl.GetLogicalParent()); + Assert.Equal(target, newControl.GetLogicalParent()); + } + }; - target.Content = newControl; - Threading.Dispatcher.UIThread.RunJobs(); + target.Content = newControl; + Threading.Dispatcher.UIThread.RunJobs(); + } } } public class TestTransition : IPageTransition From 21f6307c379ff98f255dc5a141f5c5ad5e83a9ba Mon Sep 17 00:00:00 2001 From: daniel Date: Wed, 28 Dec 2022 20:21:20 +0200 Subject: [PATCH 110/213] again --- .../TransitioningContentControlTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs index ac944352c2..aaa1de4da4 100644 --- a/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TransitioningContentControlTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() { - using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) + using (UnitTestApplication.Start(TestServices.StyledWindow)) { var testTransition = new TestTransition(); From 8d5d382c2f134779d8f49e17a71064f664029e06 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 28 Dec 2022 23:52:34 -0500 Subject: [PATCH 111/213] Reuse GetVelocityEstimate arrays --- .../Input/GestureRecognizers/VelocityTracker.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 909010d43d..526d36ba6c 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -94,6 +94,11 @@ namespace Avalonia.Input.GestureRecognizers private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) private const double MaxFlingVelocity = 8000.0; + private static double[] x = new double[HistorySize]; + private static double[] y = new double[HistorySize]; + private static double[] w = new double[HistorySize]; + private static double[] time = new double[HistorySize]; + private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; private int _index = 0; @@ -120,10 +125,6 @@ namespace Avalonia.Input.GestureRecognizers /// Returns null if there is no data on which to base an estimate. protected virtual VelocityEstimate? GetVelocityEstimate() { - double[] x = new double[HistorySize]; - double[] y = new double[HistorySize]; - double[] w = new double[HistorySize]; - double[] time = new double[HistorySize]; int sampleCount = 0; int index = _index; From 4645dcaa12510fe32e35e735a228c9b95e349726 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 28 Dec 2022 23:53:54 -0500 Subject: [PATCH 112/213] Use C# indegers --- .../GestureRecognizers/VelocityTracker.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 526d36ba6c..9e220bc85e 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -266,10 +266,10 @@ namespace Avalonia.Input.GestureRecognizers _Matrix a = new _Matrix(n, m); for (int h = 0; h < m; h += 1) { - a.Set(0, h, w[h]); + a[0, h] = w[h]; for (int i = 1; i < n; i += 1) { - a.Set(i, h, a.Get(i - 1, h) * x[h]); + a[i, h] = a[i - 1, h] * x[h]; } } @@ -283,14 +283,14 @@ namespace Avalonia.Input.GestureRecognizers { for (int h = 0; h < m; h += 1) { - q.Set(j, h, a.Get(j, h)); + q[j, h] = a[j, h]; } for (int i = 0; i < j; i += 1) { double dot = q.GetRow(j) * q.GetRow(i); for (int h = 0; h < m; h += 1) { - q.Set(j, h, q.Get(j, h) - dot * q.Get(i, h)); + q[j, h] = q[j, h] - dot * q[i, h]; } } @@ -304,7 +304,7 @@ namespace Avalonia.Input.GestureRecognizers double inverseNorm = 1.0 / norm; for (int h = 0; h < m; h += 1) { - q.Set(j, h, q.Get(j, h) * inverseNorm); + q[j, h] = q[j, h] * inverseNorm; } for (int i = 0; i < n; i += 1) { @@ -324,9 +324,9 @@ namespace Avalonia.Input.GestureRecognizers result.Coefficients[i] = q.GetRow(i) * wy; for (int j = n - 1; j > i; j -= 1) { - result.Coefficients[i] -= r.Get(i, j) * result.Coefficients[j]; + result.Coefficients[i] -= r[i, j] * result.Coefficients[j]; } - result.Coefficients[i] /= r.Get(i, i); + result.Coefficients[i] /= r[i, i]; } // Calculate the coefficient of determination (confidence) as: @@ -413,10 +413,10 @@ namespace Avalonia.Input.GestureRecognizers _elements = new double[rows * cols]; } - public double Get(int row, int col) => _elements[row * _columns + col]; - public void Set(int row, int col, double value) + public double this[int row, int col] { - _elements[row * _columns + col] = value; + get => _elements[row * _columns + col]; + set => _elements[row * _columns + col] = value; } public _Vector GetRow(int row) => new(_elements, row * _columns, _columns); From 6d6ec49dd3ff93e76625027efe2e4f3897321dd3 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 29 Dec 2022 00:09:58 -0500 Subject: [PATCH 113/213] Use Span instead of _Vector --- .../GestureRecognizers/VelocityTracker.cs | 59 ++++++------------- 1 file changed, 18 insertions(+), 41 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 9e220bc85e..d290faf114 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -275,7 +275,7 @@ namespace Avalonia.Input.GestureRecognizers // Apply the Gram-Schmidt process to A to obtain its QR decomposition. - // Orthonormal basis, column-major ordVectorer. + // Orthonormal basis, column-major order Vector. _Matrix q = new _Matrix(n, m); // Upper triangular matrix, row-major order. _Matrix r = new _Matrix(n, n); @@ -287,14 +287,14 @@ namespace Avalonia.Input.GestureRecognizers } for (int i = 0; i < j; i += 1) { - double dot = q.GetRow(j) * q.GetRow(i); + double dot = Multiply(q.GetRow(j), q.GetRow(i)); for (int h = 0; h < m; h += 1) { q[j, h] = q[j, h] - dot * q[i, h]; } } - double norm = q.GetRow(j).Norm(); + double norm = Norm(q.GetRow(j)); if (norm < PrecisionErrorTolerance) { // Vectors are linearly dependent or zero so no solution. @@ -308,20 +308,21 @@ namespace Avalonia.Input.GestureRecognizers } for (int i = 0; i < n; i += 1) { - r.Set(j, i, i < j ? 0.0 : q.GetRow(j) * a.GetRow(i)); + r[j, i] = i < j ? 0.0 : Multiply(q.GetRow(j), a.GetRow(i)); } } // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. // We just work from bottom-right to top-left calculating B's coefficients. - _Vector wy = new _Vector(m); + // "m" isn't expected to be bigger than HistorySize=20, so allocation on stack is safe. + Span wy = stackalloc double[m]; for (int h = 0; h < m; h += 1) { wy[h] = y[h] * w[h]; } for (int i = n - 1; i >= 0; i -= 1) { - result.Coefficients[i] = q.GetRow(i) * wy; + result.Coefficients[i] = Multiply(q.GetRow(i), wy); for (int j = n - 1; j > i; j -= 1) { result.Coefficients[i] -= r[i, j] * result.Coefficients[j]; @@ -363,43 +364,19 @@ namespace Avalonia.Input.GestureRecognizers return result; } - private readonly struct _Vector + private static double Multiply(Span v1, Span v2) { - private readonly int _offset; - private readonly int _length; - private readonly double[] _elements; - - internal _Vector(int size) - { - _offset = 0; - _length = size; - _elements = new double[size]; - } - - internal _Vector(double[] values, int offset, int length) - { - _offset = offset; - _length = length; - _elements = values; - } - - public double this[int i] - { - get => _elements[i + _offset]; - set => _elements[i + _offset] = value; - } - - public static double operator *(_Vector a, _Vector b) + double result = 0.0; + for (int i = 0; i < v1.Length; i += 1) { - double result = 0.0; - for (int i = 0; i < a._length; i += 1) - { - result += a[i] * b[i]; - } - return result; + result += v1[i] * v2[i]; } - - public double Norm() => Math.Sqrt(this * this); + return result; + } + + private static double Norm(Span v) + { + return Math.Sqrt(Multiply(v, v)); } private readonly struct _Matrix @@ -419,7 +396,7 @@ namespace Avalonia.Input.GestureRecognizers set => _elements[row * _columns + col] = value; } - public _Vector GetRow(int row) => new(_elements, row * _columns, _columns); + public Span GetRow(int row) => _elements.AsSpan(row * _columns, _columns); } } } From ebdf7df1624fe826211c0facb8968be19000c8c6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 29 Dec 2022 00:21:00 -0500 Subject: [PATCH 114/213] Use stackalloc instead of reused arrays, so this code can potentially work with multitouch This reverts commit 8d5d382c2f134779d8f49e17a71064f664029e06. --- .../Input/GestureRecognizers/VelocityTracker.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index d290faf114..d4705bcdca 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -30,6 +30,7 @@ using System; using System.Diagnostics; +using Avalonia.Utilities; namespace Avalonia.Input.GestureRecognizers { @@ -94,11 +95,6 @@ namespace Avalonia.Input.GestureRecognizers private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) private const double MaxFlingVelocity = 8000.0; - private static double[] x = new double[HistorySize]; - private static double[] y = new double[HistorySize]; - private static double[] w = new double[HistorySize]; - private static double[] time = new double[HistorySize]; - private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; private int _index = 0; @@ -125,6 +121,10 @@ namespace Avalonia.Input.GestureRecognizers /// Returns null if there is no data on which to base an estimate. protected virtual VelocityEstimate? GetVelocityEstimate() { + Span x = stackalloc double[HistorySize]; + Span y = stackalloc double[HistorySize]; + Span w = stackalloc double[HistorySize]; + Span time = stackalloc double[HistorySize]; int sampleCount = 0; int index = _index; @@ -168,10 +168,10 @@ namespace Avalonia.Input.GestureRecognizers if (sampleCount >= MinSampleSize) { - var xFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), x.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + var xFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), x.Slice(0, sampleCount), w.Slice(0, sampleCount)); if (xFit != null) { - var yFit = LeastSquaresSolver.Solve(2, time.AsSpan(0, sampleCount), y.AsSpan(0, sampleCount), w.AsSpan(0, sampleCount)); + var yFit = LeastSquaresSolver.Solve(2, time.Slice(0, sampleCount), y.Slice(0, sampleCount), w.Slice(0, sampleCount)); if (yFit != null) { return new VelocityEstimate( // convert from pixels/ms to pixels/s From 899dc69d7283d86c9d5189d5d4fa51e1d383b0e2 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:02:11 +0100 Subject: [PATCH 115/213] removed unnecessary comments --- .../ScrollGestureRecognizer.cs | 2 +- .../GestureRecognizers/VelocityTracker.cs | 31 ++----------------- 2 files changed, 3 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index e2deea0fde..cec98ec66b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -113,7 +113,7 @@ namespace Avalonia.Input.GestureRecognizers _scrolling = true; if (_scrolling) { - _velocityTracker = new VelocityTracker(); // TODO: Should be platform specific -- this default tracker is for Android. + _velocityTracker = new VelocityTracker(); // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance _trackedRootPoint = new Point( diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index d4705bcdca..9482542b45 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -1,40 +1,13 @@ // Code in this file is derived from // https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/gestures/velocity_tracker.dart -//Copyright 2014 The Flutter Authors. All rights reserved. - -//Redistribution and use in source and binary forms, with or without modification, -//are permitted provided that the following conditions are met: - -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following -// disclaimer in the documentation and/or other materials provided -// with the distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived -// from this software without specific prior written permission. - -//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -//LOSS OF USE, DATA, OR PROFITS; -//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - using System; using System.Diagnostics; using Avalonia.Utilities; namespace Avalonia.Input.GestureRecognizers { - // TODO: add 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? + // Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? public readonly record struct Velocity(Vector PixelsPerSecond) { @@ -92,7 +65,7 @@ namespace Avalonia.Input.GestureRecognizers private const int HistorySize = 20; private const int HorizonMilliseconds = 100; private const int MinSampleSize = 3; - private const double MinFlingVelocity = 50.0; // Logical pixels / second (defined in flutter\lib\src\gesture\constants.dart) + private const double MinFlingVelocity = 50.0; // Logical pixels / second private const double MaxFlingVelocity = 8000.0; private readonly PointAtTime[] _samples = new PointAtTime[HistorySize]; From 9332fef8af52bc5eea6555354613187612eccbea Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:05:11 +0100 Subject: [PATCH 116/213] flutter added to notice.md --- NOTICE.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/NOTICE.md b/NOTICE.md index e97fc654c9..bd26b65d70 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -303,3 +303,34 @@ https://github.com/chromium/chromium // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Flutter + +https://github.com/flutter/flutter + +//Copyright 2014 The Flutter Authors. All rights reserved. + +//Redistribution and use in source and binary forms, with or without modification, +//are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. + +//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +//WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +//DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +//ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +//(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +//LOSS OF USE, DATA, OR PROFITS; +//OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +//ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +//(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +//SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 0ad6dec3a9e43e7533298f7166b6cd475c5ec781 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:17:51 +0100 Subject: [PATCH 117/213] VelocityTracker types marked as internal --- .../Input/GestureRecognizers/VelocityTracker.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 9482542b45..f72ac61c4b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -9,7 +9,7 @@ namespace Avalonia.Input.GestureRecognizers { // Possible enhancement: add Flutter's 'IOSScrollViewFlingVelocityTracker' and 'MacOSScrollViewFlingVelocityTracker'? - public readonly record struct Velocity(Vector PixelsPerSecond) + internal readonly record struct Velocity(Vector PixelsPerSecond) { public Velocity ClampMagnitude(double minValue, double maxValue) { @@ -45,7 +45,7 @@ namespace Avalonia.Input.GestureRecognizers /// * [VelocityTracker], which computes [VelocityEstimate]s. /// * [Velocity], which encapsulates (just) a velocity vector and provides some /// useful velocity operations. - public record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset); + internal record VelocityEstimate(Vector PixelsPerSecond, double Confidence, TimeSpan Duration, Vector Offset); internal record struct PointAtTime(bool Valid, Vector Point, TimeSpan Time); @@ -59,7 +59,7 @@ namespace Avalonia.Input.GestureRecognizers /// /// The quality of the velocity estimation will be better if more data points /// have been received. - public class VelocityTracker + internal class VelocityTracker { private const int AssumePointerMoveStoppedMilliseconds = 40; private const int HistorySize = 20; @@ -177,7 +177,7 @@ namespace Avalonia.Input.GestureRecognizers /// estimate or if the estimated velocity is zero./// /// /// - public Velocity GetVelocity() + internal Velocity GetVelocity() { var estimate = GetVelocityEstimate(); if (estimate == null || estimate.PixelsPerSecond.IsDefault) @@ -187,7 +187,7 @@ namespace Avalonia.Input.GestureRecognizers return new Velocity(estimate.PixelsPerSecond); } - public virtual Velocity GetFlingVelocity() + internal virtual Velocity GetFlingVelocity() { return GetVelocity().ClampMagnitude(MinFlingVelocity, MaxFlingVelocity); } From 157ac02dc661f2ddb23b615545ed825626eae9de Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 10:58:26 +0100 Subject: [PATCH 118/213] _Matrix memory optimized using stack --- .../Input/GestureRecognizers/VelocityTracker.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index f72ac61c4b..206a73f436 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -236,7 +236,7 @@ namespace Avalonia.Input.GestureRecognizers int n = degree + 1; // Expand the X vector to a matrix A, pre-multiplied by the weights. - _Matrix a = new _Matrix(n, m); + _Matrix a = new _Matrix(m, stackalloc double[n * m]); for (int h = 0; h < m; h += 1) { a[0, h] = w[h]; @@ -249,9 +249,9 @@ namespace Avalonia.Input.GestureRecognizers // Apply the Gram-Schmidt process to A to obtain its QR decomposition. // Orthonormal basis, column-major order Vector. - _Matrix q = new _Matrix(n, m); + _Matrix q = new _Matrix(m, stackalloc double[n * m]); // Upper triangular matrix, row-major order. - _Matrix r = new _Matrix(n, n); + _Matrix r = new _Matrix(n, stackalloc double[n * n]); for (int j = 0; j < n; j += 1) { for (int h = 0; h < m; h += 1) @@ -352,15 +352,15 @@ namespace Avalonia.Input.GestureRecognizers return Math.Sqrt(Multiply(v, v)); } - private readonly struct _Matrix + private readonly ref struct _Matrix { private readonly int _columns; - private readonly double[] _elements; + private readonly Span _elements; - internal _Matrix(int rows, int cols) + internal _Matrix(int cols, Span elements) { _columns = cols; - _elements = new double[rows * cols]; + _elements = elements;// new double[rows * cols]; } public double this[int row, int col] @@ -369,7 +369,7 @@ namespace Avalonia.Input.GestureRecognizers set => _elements[row * _columns + col] = value; } - public Span GetRow(int row) => _elements.AsSpan(row * _columns, _columns); + public Span GetRow(int row) => _elements.Slice(row * _columns, _columns); } } } From c7c819295f82706d6f6ec0546ce9c83d1e095605 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 29 Dec 2022 11:26:28 +0100 Subject: [PATCH 119/213] removed comment --- src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs index 206a73f436..ce41aa6308 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/VelocityTracker.cs @@ -360,7 +360,7 @@ namespace Avalonia.Input.GestureRecognizers internal _Matrix(int cols, Span elements) { _columns = cols; - _elements = elements;// new double[rows * cols]; + _elements = elements; } public double this[int row, int col] From c099e2fd2c5e9924e98446bc34b0c79fb1870a7c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 30 Dec 2022 19:14:38 +0600 Subject: [PATCH 120/213] Use for(...) dst[c]=src[c] loop on arm due to a JIT bug --- .../Composition/Transport/BatchStream.cs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index a3cad3cebd..2dd15dfedc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Avalonia.Rendering.Composition.Animations; using Avalonia.Rendering.Composition.Server; @@ -27,6 +28,37 @@ public record struct BatchStreamSegment public int ElementCount { get; set; } } + +// Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm, +// see https://github.com/dotnet/runtime/issues/80068 +static unsafe class UnalignedMemoryHelper +{ + public static T ReadUnaligned(byte* src) where T : unmanaged + { +#if NET6_0_OR_GREATER + Unsafe.SkipInit(out var rv); +#else + T rv; +#endif + UnalignedMemcpy((byte*)&rv, src, Unsafe.SizeOf()); + return rv; + } + + public static void WriteUnaligned(byte* dst, T value) where T : unmanaged + { + UnalignedMemcpy(dst, (byte*)&value, Unsafe.SizeOf()); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static unsafe void UnalignedMemcpy(byte* dst, byte* src, int count) + { + for (var c = 0; c < count; c++) + { + dst[c] = src[c]; + } + } +} + internal class BatchStreamWriter : IDisposable { private readonly BatchStreamData _output; @@ -74,7 +106,15 @@ internal class BatchStreamWriter : IDisposable var size = Unsafe.SizeOf(); if (_currentDataSegment.Data == IntPtr.Zero || _currentDataSegment.ElementCount + size > _memoryPool.BufferSize) NextDataSegment(); - Unsafe.WriteUnaligned((byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount, item); + var ptr = (byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount; + + // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32, + // see https://github.com/dotnet/runtime/issues/80068 + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) + UnalignedMemoryHelper.WriteUnaligned(ptr, item); + else + *(T*)ptr = item; + _currentDataSegment.ElementCount += size; } @@ -125,7 +165,16 @@ internal class BatchStreamReader : IDisposable if (_memoryOffset + size > _currentDataSegment.ElementCount) throw new InvalidOperationException("Attempted to read more memory then left in the current segment"); - var rv = Unsafe.ReadUnaligned((byte*)_currentDataSegment.Data + _memoryOffset); + var ptr = (byte*)_currentDataSegment.Data + _memoryOffset; + T rv; + + // Unsafe.ReadUnaligned/Unsafe.WriteUnaligned are broken on arm32, + // see https://github.com/dotnet/runtime/issues/80068 + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) + rv = UnalignedMemoryHelper.ReadUnaligned(ptr); + else + rv = *(T*)ptr; + _memoryOffset += size; if (_memoryOffset == _currentDataSegment.ElementCount) { From 617c57376a34aa2511caa4b8d39283dbe4d9c801 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 30 Dec 2022 21:49:37 +0600 Subject: [PATCH 121/213] Use Unsafe.Read/WriteUnaligned for other platforms since it might be required for something like wasm --- .../Rendering/Composition/Transport/BatchStream.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs index 2dd15dfedc..5a88f0e91d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -113,7 +113,7 @@ internal class BatchStreamWriter : IDisposable if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) UnalignedMemoryHelper.WriteUnaligned(ptr, item); else - *(T*)ptr = item; + Unsafe.WriteUnaligned(ptr, item); _currentDataSegment.ElementCount += size; } @@ -173,7 +173,7 @@ internal class BatchStreamReader : IDisposable if (RuntimeInformation.ProcessArchitecture == Architecture.Arm) rv = UnalignedMemoryHelper.ReadUnaligned(ptr); else - rv = *(T*)ptr; + rv = Unsafe.ReadUnaligned(ptr); _memoryOffset += size; if (_memoryOffset == _currentDataSegment.ElementCount) From 50c21198b4032faa0b8d188383a003ff830ced9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Dec 2022 22:04:09 +0000 Subject: [PATCH 122/213] Bump flat and mocha Bumps [flat](https://github.com/hughsk/flat) to 5.0.2 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together. Updates `flat` from 4.1.0 to 5.0.2 - [Release notes](https://github.com/hughsk/flat/releases) - [Commits](https://github.com/hughsk/flat/compare/4.1.0...5.0.2) Updates `mocha` from 8.1.3 to 8.4.0 - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v8.1.3...v8.4.0) --- updated-dependencies: - dependency-name: flat dependency-type: indirect - dependency-name: mocha dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- .../HtmlTransport/webapp/package-lock.json | 791 ++++++------------ .../Remote/HtmlTransport/webapp/package.json | 2 +- 2 files changed, 250 insertions(+), 543 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index eb57cfb8da..272be1c8cb 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -372,6 +372,12 @@ "csstype": "^3.0.2" } }, + "@ungap/promise-all-settled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", + "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", + "dev": true + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -405,9 +411,9 @@ } }, "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "requires": { "normalize-path": "^3.0.0", @@ -444,18 +450,6 @@ "sprintf-js": "~1.0.2" } }, - "array.prototype.map": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.2.tgz", - "integrity": "sha512-Az3OYxgsa1g7xDYp86l0nnN4bcmuEITGe1rbdEBVkrqkzMgDcbdQ2R7r41pNzti+4NMces3H8gMmuioZUilLgw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.4" - } - }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -469,9 +463,9 @@ "dev": true }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "brace-expansion": { @@ -538,9 +532,9 @@ } }, "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -554,19 +548,19 @@ "dev": true }, "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" + "readdirp": "~3.5.0" } }, "clean-stack": { @@ -576,40 +570,46 @@ "dev": true }, "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", "dev": true, "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" }, "dependencies": { "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" } } } @@ -708,15 +708,6 @@ "strip-bom": "^4.0.0" } }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -724,68 +715,23 @@ "dev": true }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "es-get-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.0.tgz", - "integrity": "sha512-UfrmHuWQlNMTs35e1ypnvikg6jCz3SK8v8ImvmDsh36fCVUR1MqoFDiyn0/k52C8NqO3YsO8Oe0azeesNuqSsQ==", - "dev": true, - "requires": { - "es-abstract": "^1.17.4", - "has-symbols": "^1.0.1", - "is-arguments": "^1.0.4", - "is-map": "^2.0.1", - "is-set": "^2.0.1", - "is-string": "^1.0.5", - "isarray": "^2.0.5" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -829,13 +775,10 @@ } }, "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true }, "foreground-child": { "version": "2.0.0", @@ -860,18 +803,12 @@ "dev": true }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "gensync": { "version": "1.0.0-beta.1", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", @@ -911,9 +848,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "requires": { "is-glob": "^4.0.1" @@ -937,27 +874,12 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, "hasha": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.0.tgz", @@ -1008,12 +930,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", - "dev": true - }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -1023,51 +939,27 @@ "binary-extensions": "^2.0.0" } }, - "is-buffer": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", - "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", - "dev": true - }, - "is-callable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", - "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", - "dev": true - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" } }, - "is-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.1.tgz", - "integrity": "sha512-T/S49scO8plUiAOA2DBTBG3JHpn1yiw0kRp6dgiZ0v2/6twi5eiB0rHtHFH9ZIrvlWc6+4O+m4zg5+Z833aXgw==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1075,24 +967,9 @@ "dev": true }, "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-set": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.1.tgz", - "integrity": "sha512-eJEzOtVyenDs1TMzSQ3kU3K+E0GUS9sno+F0OBT97xsgcJsF9nXMBtkT9/kut5JEpM7oL7X/0qxR17K3mcwIAA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true }, "is-stream": { @@ -1101,21 +978,6 @@ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", "dev": true }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -1128,12 +990,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true - }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1222,22 +1078,6 @@ "istanbul-lib-report": "^3.0.0" } }, - "iterate-iterator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.1.tgz", - "integrity": "sha512-3Q6tudGN05kbkDQDI4CqjaBf4qf85w6W6GnuZDtUVYwKgtC1q8yxYX7CZed7N+tLzQqS6roujWvszf13T+n9aw==", - "dev": true - }, - "iterate-value": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", - "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", - "dev": true, - "requires": { - "es-get-iterator": "^1.0.2", - "iterate-iterator": "^1.0.1" - } - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1339,36 +1179,91 @@ "dev": true }, "mocha": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.1.3.tgz", - "integrity": "sha512-ZbaYib4hT4PpF4bdSO2DohooKXIn4lDeiYqB+vTmCdr6l2woW0b6H3pf5x4sM5nwQMru9RvjjHYWVGltR50ZBw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", + "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", "dev": true, "requires": { + "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.4.2", - "debug": "4.1.1", - "diff": "4.0.2", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", "glob": "7.1.6", "growl": "1.10.5", "he": "1.2.0", - "js-yaml": "3.14.0", + "js-yaml": "4.0.0", "log-symbols": "4.0.0", "minimatch": "3.0.4", - "ms": "2.1.2", - "object.assign": "4.1.0", - "promise.allsettled": "1.0.2", - "serialize-javascript": "4.0.0", - "strip-json-comments": "3.0.1", - "supports-color": "7.1.0", + "ms": "2.1.3", + "nanoid": "3.1.20", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", "which": "2.0.2", "wide-align": "1.1.3", - "workerpool": "6.0.0", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.1" + "workerpool": "6.1.0", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "js-yaml": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", + "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "moq.ts": { @@ -1386,6 +1281,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nanoid": { + "version": "3.1.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", + "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "dev": true + }, "node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -1570,30 +1471,6 @@ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1604,12 +1481,12 @@ } }, "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" } }, "p-locate": { @@ -1679,9 +1556,9 @@ "dev": true }, "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pkg-dir": { @@ -1741,19 +1618,6 @@ "fromentries": "^1.2.0" } }, - "promise.allsettled": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.2.tgz", - "integrity": "sha512-UpcYW5S1RaNKT6pd+s9jp9K9rlQge1UXKskec0j6Mmuq7UJCvlS2J2/s/yuPN8ehftf9HXMxWlKiPbGGUzpoRg==", - "dev": true, - "requires": { - "array.prototype.map": "^1.0.1", - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "iterate-value": "^1.0.0" - } - }, "prop-types": { "version": "15.7.2", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", @@ -1792,9 +1656,9 @@ "dev": true }, "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -1858,9 +1722,9 @@ "dev": true }, "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -1947,30 +1811,10 @@ "strip-ansi": "^4.0.0" } }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -1983,9 +1827,9 @@ "dev": true }, "strip-json-comments": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", - "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, "supports-color": { @@ -2100,70 +1944,52 @@ } }, "workerpool": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.0.0.tgz", - "integrity": "sha512-fU2OcNA/GVAJLLyKUoHkAgIhKb0JoCpSjLC/G2vYKxUjVmQwGbRVeoPJ1a8U4pnVofz4AQV5Y/NEw8oKqxEBtA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", + "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", "dev": true }, "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "dependencies": { "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" } } } @@ -2193,214 +2019,89 @@ "dev": true }, "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", "dev": true, "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, "dependencies": { "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { + "is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.1" } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true } } }, "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true }, "yargs-unparser": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.1.tgz", - "integrity": "sha512-qZV14lK9MWsGCmcr7u5oXGH0dbGqZAIxTDrWXZDo5zUr6b6iUmelNKO6x6R1dQT24AH3LgRxJpr8meWy2unolA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, "requires": { - "camelcase": "^5.3.1", - "decamelize": "^1.2.0", - "flat": "^4.1.0", - "is-plain-obj": "^1.1.0", - "yargs": "^14.2.3" + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" }, "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "yargs": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.3.tgz", - "integrity": "sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^15.0.1" - } - }, - "yargs-parser": { - "version": "15.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz", - "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -2409,6 +2110,12 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json index dbb12e192a..da3a748c39 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json @@ -16,7 +16,7 @@ "@types/mocha": "8.0.3", "@types/react": "^16.3.14", "chai": "^4.2.0", - "mocha": "^8.1.3", + "mocha": "^8.4.0", "moq.ts": "^6.4.0", "nyc": "^15.1.0", "react": "^16.3.2", From bad0160a2d74b30f041fbe474a5c81e29ee5020d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 02:08:17 +0000 Subject: [PATCH 123/213] Bump json5 Bumps [json5](https://github.com/json5/json5) from 2.1.3 to 2.2.2. - [Release notes](https://github.com/json5/json5/releases) - [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md) - [Commits](https://github.com/json5/json5/compare/v2.1.3...v2.2.2) --- updated-dependencies: - dependency-name: json5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../HtmlTransport/webapp/package-lock.json | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index 272be1c8cb..7ae6c4c8d9 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -1101,13 +1101,10 @@ "dev": true }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.2.tgz", + "integrity": "sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ==", + "dev": true }, "locate-path": { "version": "6.0.0", @@ -1172,12 +1169,6 @@ "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, "mocha": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", From fb88efce5f4b980a00581a1e2626eade90e39e89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 Dec 2022 06:29:27 +0000 Subject: [PATCH 124/213] Bump nanoid and mocha Bumps [nanoid](https://github.com/ai/nanoid) to 3.3.3 and updates ancestor dependency [mocha](https://github.com/mochajs/mocha). These dependencies need to be updated together. Updates `nanoid` from 3.1.20 to 3.3.3 - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.1.20...3.3.3) Updates `mocha` from 8.4.0 to 10.2.0 - [Release notes](https://github.com/mochajs/mocha/releases) - [Changelog](https://github.com/mochajs/mocha/blob/master/CHANGELOG.md) - [Commits](https://github.com/mochajs/mocha/compare/v8.4.0...v10.2.0) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect - dependency-name: mocha dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- .../HtmlTransport/webapp/package-lock.json | 290 +++++++----------- .../Remote/HtmlTransport/webapp/package.json | 2 +- 2 files changed, 110 insertions(+), 182 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json index 7ae6c4c8d9..0c0857b680 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package-lock.json @@ -372,12 +372,6 @@ "csstype": "^3.0.2" } }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -395,9 +389,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -548,19 +542,19 @@ "dev": true }, "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { - "anymatch": "~3.1.1", + "anymatch": "~3.1.2", "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "readdirp": "~3.6.0" } }, "clean-stack": { @@ -578,40 +572,6 @@ "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "color-convert": { @@ -868,12 +828,6 @@ "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -946,9 +900,9 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { @@ -984,6 +938,12 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -1128,12 +1088,13 @@ "dev": true }, "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "requires": { - "chalk": "^4.0.0" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" } }, "loose-envify": { @@ -1170,33 +1131,29 @@ } }, "mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", "dev": true, "requires": { - "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", + "glob": "7.2.0", "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" @@ -1209,9 +1166,9 @@ "dev": true }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -1231,15 +1188,60 @@ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", "dev": true }, + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "dependencies": { + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "requires": { "argparse": "^2.0.1" } }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + } + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -1273,9 +1275,9 @@ "dev": true }, "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, "node-preload": { @@ -1647,9 +1649,9 @@ "dev": true }, "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "requires": { "picomatch": "^2.2.1" @@ -1713,9 +1715,9 @@ "dev": true }, "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -1793,22 +1795,23 @@ "dev": true }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^5.0.1" } }, "strip-bom": { @@ -1925,19 +1928,10 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { @@ -1949,40 +1943,6 @@ "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } } }, "wrappy": { @@ -2024,38 +1984,6 @@ "yargs-parser": "^20.2.2" }, "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json index da3a748c39..580c4059ba 100644 --- a/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json +++ b/tests/Avalonia.DesignerSupport.Tests/Remote/HtmlTransport/webapp/package.json @@ -16,7 +16,7 @@ "@types/mocha": "8.0.3", "@types/react": "^16.3.14", "chai": "^4.2.0", - "mocha": "^8.4.0", + "mocha": "^10.2.0", "moq.ts": "^6.4.0", "nyc": "^15.1.0", "react": "^16.3.2", From 64c12e9253914c4516a50333ae76eb3a00ff0273 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Sat, 31 Dec 2022 14:57:59 +0100 Subject: [PATCH 125/213] [AndroidMotionEventsHelper] fixes eventTime to uptime android in milliseconds --- .../Platform/Specific/Helpers/AndroidMotionEventsHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs index 6d0e6be0ad..3d0f3b2652 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs @@ -38,7 +38,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers return null; } - var eventTime = (ulong)DateTime.Now.Millisecond; + var eventTime = (ulong)e.EventTime; var inputRoot = _view.InputRoot; var actionMasked = e.ActionMasked; var modifiers = GetModifiers(e.MetaState, e.ButtonState); From dcb4f759d16674078accdee887edf39568d499b2 Mon Sep 17 00:00:00 2001 From: daniel Date: Sun, 1 Jan 2023 13:58:14 +0200 Subject: [PATCH 126/213] Make FlowDirection's Path of CheckBox to fixed LTR --- src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml index 9abcf5d32b..28f8649e2d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml @@ -50,7 +50,8 @@ Opacity="0" Fill="{DynamicResource CheckBoxCheckGlyphForegroundUnchecked}" Stretch="Uniform" - VerticalAlignment="Center" /> + VerticalAlignment="Center" + FlowDirection="LeftToRight" /> @@ -146,7 +147,6 @@ - From 5a3d9f15743d6e5caf75e5e5fe96898fe0d30e1c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 1 Jan 2023 19:18:32 +0600 Subject: [PATCH 127/213] Use parent clip rect of the adorned visual --- .../Server/ServerCompositionVisual.cs | 19 ++++- .../Controls/AdornerTests.cs | 73 ++++++++++++++++++ tests/Avalonia.RenderTests/TestBase.cs | 9 ++- ...s_Adorner_Is_Properly_Clipped.expected.png | Bin 0 -> 673 bytes ...s_Adorner_Is_Properly_Clipped.expected.png | Bin 0 -> 673 bytes 5 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 tests/Avalonia.RenderTests/Controls/AdornerTests.cs create mode 100644 tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png create mode 100644 tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 0e15cbd54b..0b2df6d2b3 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -42,11 +42,18 @@ namespace Avalonia.Rendering.Composition.Server Root!.RenderedVisuals++; + if (Opacity != 1) + canvas.PushOpacity(Opacity); + if (AdornedVisual != null) + { + canvas.PostTransform = Matrix.Identity; + canvas.Transform = Matrix.Identity; + canvas.PushClip(AdornedVisual._combinedTransformedClipBounds); + } var transform = GlobalTransformMatrix; canvas.PostTransform = MatrixUtils.ToMatrix(transform); canvas.Transform = Matrix.Identity; - if (Opacity != 1) - canvas.PushOpacity(Opacity); + var boundsRect = new Rect(new Size(Size.X, Size.Y)); if (ClipToBounds && !HandlesClipToBounds) canvas.PushClip(Root!.SnapToDevicePixels(boundsRect)); @@ -67,6 +74,8 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopGeometryClip(); if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); + if (AdornedVisual != null) + canvas.PopClip(); if(Opacity != 1) canvas.PopOpacity(); } @@ -155,8 +164,12 @@ namespace Avalonia.Rendering.Composition.Server _clipSizeDirty = false; } + + _combinedTransformedClipBounds = + AdornedVisual?._combinedTransformedClipBounds + ?? Parent?._combinedTransformedClipBounds + ?? new Rect(Root!.Size); - _combinedTransformedClipBounds = Parent?._combinedTransformedClipBounds ?? new Rect(Root!.Size); if (_transformedClipBounds != null) _combinedTransformedClipBounds = _combinedTransformedClipBounds.Intersect(_transformedClipBounds.Value); diff --git a/tests/Avalonia.RenderTests/Controls/AdornerTests.cs b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs new file mode 100644 index 0000000000..c833017212 --- /dev/null +++ b/tests/Avalonia.RenderTests/Controls/AdornerTests.cs @@ -0,0 +1,73 @@ +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Layout; +using Avalonia.Media; +using Xunit; + +#if AVALONIA_SKIA +namespace Avalonia.Skia.RenderTests; +#else +namespace Avalonia.Direct2D1.RenderTests.Controls; +#endif + +public class AdornerTests : TestBase +{ + public AdornerTests() + : base(@"Controls\Adorner") + { + } + + [Fact] + public async Task Focus_Adorner_Is_Properly_Clipped() + { + Border adorned; + var tree = new Decorator + { + Child = new VisualLayerManager + { + Child = new Border + { + Background = Brushes.Red, + Padding = new Thickness(10, 50, 10,10), + Child = new Border() + { + Background = Brushes.White, + ClipToBounds = true, + Padding = new Thickness(0, -30, 0, 0), + Child = adorned = new Border + { + Background = Brushes.Green, + VerticalAlignment = VerticalAlignment.Top, + Height = 100, + Width = 50 + } + } + } + }, + Width = 200, + Height = 200 + }; + var adorner = new Border + { + BorderThickness = new Thickness(2), + BorderBrush = Brushes.Black + }; + + var size = new Size(tree.Width, tree.Height); + tree.Measure(size); + tree.Arrange(new Rect(size)); + + + adorned.AttachedToVisualTree += delegate + { + AdornerLayer.SetAdornedElement(adorner, adorned); + AdornerLayer.GetAdornerLayer(adorned)!.Children.Add(adorner); + }; + tree.Measure(size); + tree.Arrange(new Rect(size)); + + await RenderToFile(tree); + CompareImages(skipImmediate: true, skipDeferred: true); + } +} \ No newline at end of file diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 8a127897d7..313281d6c6 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -156,7 +156,8 @@ namespace Avalonia.Direct2D1.RenderTests public ILockedFramebuffer Lock() => _bitmap.Lock(); } - protected void CompareImages([CallerMemberName] string testName = "") + protected void CompareImages([CallerMemberName] string testName = "", + bool skipImmediate = false, bool skipDeferred = false, bool skipCompositor = false) { var expectedPath = Path.Combine(OutputPath, testName + ".expected.png"); var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); @@ -172,17 +173,17 @@ namespace Avalonia.Direct2D1.RenderTests var deferredError = CompareImages(deferred, expected); var compositedError = CompareImages(composited, expected); - if (immediateError > 0.022) + if (immediateError > 0.022 && !skipImmediate) { Assert.True(false, immediatePath + ": Error = " + immediateError); } - if (deferredError > 0.022) + if (deferredError > 0.022 && !skipDeferred) { Assert.True(false, deferredPath + ": Error = " + deferredError); } - if (compositedError > 0.022) + if (compositedError > 0.022 && !skipCompositor) { Assert.True(false, compositedPath + ": Error = " + compositedError); } diff --git a/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Direct2D1/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6a67087d41828128492aa2cb44f7d53f067db4f1 GIT binary patch literal 673 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`0|Qfpr;B4q z#hkZSH+nfc3OHPhJ=y;J{BfllC%-D5QmgWtEE3Jb^Zo3+t!a$&tE_DqY>XQn1UOje zPqf^AI^B4`#j)%g)%O4H^!I(N{eJxXji*uayZ)-!D`Iz4IT|2zRSq{DZ!m$3x^zZ)*9ThlOnjD4(@g_3ckDu}UDb_{>0S+c~;sf9P X|Dv4+8exvWG{@lS>gTe~DWM4f2)3Ck literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png b/tests/TestFiles/Skia/Controls/Adorner/Focus_Adorner_Is_Properly_Clipped.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..6a67087d41828128492aa2cb44f7d53f067db4f1 GIT binary patch literal 673 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yu@pObhHwBu4M$1`0|Qfpr;B4q z#hkZSH+nfc3OHPhJ=y;J{BfllC%-D5QmgWtEE3Jb^Zo3+t!a$&tE_DqY>XQn1UOje zPqf^AI^B4`#j)%g)%O4H^!I(N{eJxXji*uayZ)-!D`Iz4IT|2zRSq{DZ!m$3x^zZ)*9ThlOnjD4(@g_3ckDu}UDb_{>0S+c~;sf9P X|Dv4+8exvWG{@lS>gTe~DWM4f2)3Ck literal 0 HcmV?d00001 From 78b4c6a96e80b8ac6f9a103c71154bd3d8caea20 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 1 Jan 2023 20:25:26 +0600 Subject: [PATCH 128/213] Ignore Opacity for hit-testing purposes --- .../Server/ServerCompositionVisual.cs | 13 ++++++-- .../Rendering/CompositorHitTestingTests.cs | 33 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index 0e15cbd54b..c4f0ebcd3b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -162,8 +162,14 @@ namespace Avalonia.Rendering.Composition.Server EffectiveOpacity = Opacity * (Parent?.EffectiveOpacity ?? 1); - IsVisibleInFrame = _parent?.IsVisibleInFrame != false && Visible && EffectiveOpacity > 0.04 && !_isBackface && - !_combinedTransformedClipBounds.IsDefault; + IsHitTestVisibleInFrame = _parent?.IsHitTestVisibleInFrame != false + && Visible + && !_isBackface + && !_combinedTransformedClipBounds.IsDefault; + + IsVisibleInFrame = IsHitTestVisibleInFrame + && _parent?.IsVisibleInFrame != false + && EffectiveOpacity > 0.04; if (wasVisible != IsVisibleInFrame || positionChanged) { @@ -187,7 +193,7 @@ namespace Avalonia.Rendering.Composition.Server readback.Revision = root.Revision; readback.Matrix = GlobalTransformMatrix; readback.TargetId = Root.Id; - readback.Visible = IsVisibleInFrame; + readback.Visible = IsHitTestVisibleInFrame; } void AddDirtyRect(Rect rc) @@ -248,6 +254,7 @@ namespace Avalonia.Rendering.Composition.Server } public bool IsVisibleInFrame { get; set; } + public bool IsHitTestVisibleInFrame { get; set; } public double EffectiveOpacity { get; set; } public Rect TransformedOwnContentBounds { get; set; } public virtual Rect OwnContentBounds => new Rect(0, 0, Size.X, Size.Y); diff --git a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs index 27faf1e13c..3d8369faeb 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/CompositorHitTestingTests.cs @@ -82,6 +82,39 @@ public class CompositorHitTestingTests : CompositorTestsBase } } + [Theory, + InlineData(false, false), + InlineData(true, false), + InlineData(false, true), + InlineData(true, true), + ] + public void HitTest_Should_Find_Zero_Opacity_Controls_At_Point(bool parent, bool child) + { + + using (var s = new CompositorServices(new Size(200, 200))) + { + Border visible, border; + s.TopLevel.Content = border = new Border + { + Width = 100, + Height = 100, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Opacity = parent ? 0 : 1, + Child = visible = new Border + { + Opacity = child ? 0 : 1, + Background = Brushes.Red, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Stretch, + } + }; + + s.AssertHitTest(new Point(100, 100), null, visible, border); + } + } + [Fact] public void HitTest_Should_Not_Find_Control_Outside_Point() { From 11b8bc2322ed4da16e115a28e3580e35a5eb4ab5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 2 Jan 2023 15:01:14 +0000 Subject: [PATCH 129/213] keep app builder instance --- .../Avalonia.Android/AvaloniaSplashActivity.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs index ec26ee5599..38038ef26c 100644 --- a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -1,6 +1,5 @@ using Android.OS; using AndroidX.AppCompat.App; -using Avalonia.Controls; namespace Avalonia.Android { @@ -8,15 +7,22 @@ namespace Avalonia.Android { protected abstract AppBuilder CreateAppBuilder(); + private static AppBuilder s_appBuilder; + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); - var builder = CreateAppBuilder(); + if (s_appBuilder == null) + { + var builder = CreateAppBuilder(); + + var lifetime = new SingleViewLifetime(); - var lifetime = new SingleViewLifetime(); + builder.SetupWithLifetime(lifetime); - builder.SetupWithLifetime(lifetime); + s_appBuilder = builder; + } } } From 5ff712109ba30fc82f4116e11691086d7cb52c98 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 3 Jan 2023 15:22:12 +1100 Subject: [PATCH 130/213] avoid nuget refs on net6 System.ValueTuple, Runtime.CompilerServices.Unsafe, and System.Memory should not be required no net6 and up --- build/Base.props | 2 +- build/System.Memory.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/Base.props b/build/Base.props index 100c9088cd..9ec1c3c2d3 100644 --- a/build/Base.props +++ b/build/Base.props @@ -1,5 +1,5 @@  - + diff --git a/build/System.Memory.props b/build/System.Memory.props index b36998a780..a413e18927 100644 --- a/build/System.Memory.props +++ b/build/System.Memory.props @@ -1,5 +1,5 @@ - + From a845fc02b66690adeae8926d9542faf5a72bb0b8 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 3 Jan 2023 12:32:31 +0100 Subject: [PATCH 131/213] fix: Obsolete --- src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGrid.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridColumn.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridRow.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridRows.cs | 4 ++-- src/Avalonia.Controls/BorderVisual.cs | 4 ++-- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs index a991f2f657..55893e9890 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/VisualNode.cs @@ -303,7 +303,7 @@ namespace Avalonia.Rendering.SceneGraph if (ClipToBounds) { context.Transform = Matrix.Identity; - if (ClipToBoundsRadius.IsEmpty) + if (ClipToBoundsRadius.IsDefault) context.PushClip(ClipBounds); else context.PushClip(new RoundedRect(ClipBounds, ClipToBoundsRadius)); diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 5dcad83601..58d5deb029 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3299,7 +3299,7 @@ namespace Avalonia.Controls newCell.IsVisible = column.IsVisible; if (row.OwningGrid.CellTheme is {} cellTheme) { - newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent); + newCell.SetValue(ThemeProperty, cellTheme, BindingPriority.Template); } } row.Cells.Insert(column.Index, newCell); diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 75101dc487..d9d80acdc3 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -897,7 +897,7 @@ namespace Avalonia.Controls result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty]; if (OwningGrid.ColumnHeaderTheme is {} columnTheme) { - result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.TemplatedParent); + result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.Template); } return result; diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 252868847a..b3e106a7bf 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -673,7 +673,7 @@ namespace Avalonia.Controls }; if (OwningGrid.ColumnHeaderTheme is {} columnHeaderTheme) { - dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.TemplatedParent); + dragIndicator.SetValue(ThemeProperty, columnHeaderTheme, BindingPriority.Template); } dragIndicator.PseudoClasses.Add(":dragIndicator"); diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index dfa6ea2e46..641360dbe4 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -253,7 +253,7 @@ namespace Avalonia.Controls }; if (OwningGrid.CellTheme is {} cellTheme) { - _fillerCell.SetValue(ThemeProperty, cellTheme, BindingPriority.TemplatedParent); + _fillerCell.SetValue(ThemeProperty, cellTheme, BindingPriority.Template); } if (_cellsElement != null) { diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 4d3bccee70..00e035270c 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -1029,7 +1029,7 @@ namespace Avalonia.Controls dataGridRow.DataContext = dataContext; if (RowTheme is {} rowTheme) { - dataGridRow.SetValue(ThemeProperty, rowTheme, BindingPriority.TemplatedParent); + dataGridRow.SetValue(ThemeProperty, rowTheme, BindingPriority.Template); } CompleteCellsCollection(dataGridRow); @@ -2743,7 +2743,7 @@ namespace Avalonia.Controls groupHeader.Level = rowGroupInfo.Level; if (RowGroupTheme is {} rowGroupTheme) { - groupHeader.SetValue(ThemeProperty, rowGroupTheme, BindingPriority.TemplatedParent); + groupHeader.SetValue(ThemeProperty, rowGroupTheme, BindingPriority.Template); } // Set the RowGroupHeader's PropertyName. Unfortunately, CollectionViewGroup doesn't have this diff --git a/src/Avalonia.Controls/BorderVisual.cs b/src/Avalonia.Controls/BorderVisual.cs index 7afbf9edcf..18448edac4 100644 --- a/src/Avalonia.Controls/BorderVisual.cs +++ b/src/Avalonia.Controls/BorderVisual.cs @@ -50,7 +50,7 @@ class CompositionBorderVisual : CompositionDrawListVisual if (ClipToBounds) { var clipRect = Root!.SnapToDevicePixels(new Rect(new Size(Size.X, Size.Y))); - if (_cornerRadius.IsEmpty) + if (_cornerRadius.IsDefault) canvas.PushClip(clipRect); else canvas.PushClip(new RoundedRect(clipRect, _cornerRadius)); @@ -73,4 +73,4 @@ class CompositionBorderVisual : CompositionDrawListVisual protected override bool HandlesClipToBounds => true; } -} \ No newline at end of file +} From bd8794dbdad7837e79f0cce7806da020baa715fd Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 3 Jan 2023 14:48:15 +0100 Subject: [PATCH 132/213] fix: Warnings CS1574 --- .../IClassicDesktopStyleApplicationLifetime.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 4b88f6b537..22b5f8236d 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using Avalonia.Metadata; namespace Avalonia.Controls.ApplicationLifetimes @@ -19,7 +18,7 @@ namespace Avalonia.Controls.ApplicationLifetimes /// /// Gets the arguments passed to the - /// + /// /// method. /// string[]? Args { get; } From 48eba14e700d3efb63100ab4bb7895e9ccac3667 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 3 Jan 2023 14:47:23 +0100 Subject: [PATCH 133/213] fix: Warnings CA1823 --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 5dcad83601..87e596d332 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -51,15 +51,10 @@ namespace Avalonia.Controls private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader"; private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader"; private const string DATAGRID_elementBottomRightCornerHeaderName = "PART_BottomRightCorner"; - private const string DATAGRID_elementValidationSummary = "PART_ValidationSummary"; private const string DATAGRID_elementVerticalScrollbarName = "PART_VerticalScrollbar"; - - private const bool DATAGRID_defaultAutoGenerateColumns = true; internal const bool DATAGRID_defaultCanUserReorderColumns = true; internal const bool DATAGRID_defaultCanUserResizeColumns = true; internal const bool DATAGRID_defaultCanUserSortColumns = true; - private const DataGridRowDetailsVisibilityMode DATAGRID_defaultRowDetailsVisibility = DataGridRowDetailsVisibilityMode.VisibleWhenSelected; - private const DataGridSelectionMode DATAGRID_defaultSelectionMode = DataGridSelectionMode.Extended; /// /// The default order to use for columns when there is no From 5c5cc06d27bc5d18c46240085bfe1c03771a1d5c Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 3 Jan 2023 14:31:56 +0100 Subject: [PATCH 134/213] fix: Warnings CS0436 conflicts with the imported type 'GetProcAddressAttribute' in 'Avalonia.Base' --- src/Avalonia.OpenGL/Avalonia.OpenGL.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj index a9136c2c99..9b38c150fa 100644 --- a/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj +++ b/src/Avalonia.OpenGL/Avalonia.OpenGL.csproj @@ -13,4 +13,9 @@ + + + + + From 306ea5b54dc10ae686b0b45af07c57d6a07cc084 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 3 Jan 2023 15:08:11 +0100 Subject: [PATCH 135/213] feat: Enable Rule CA2211 --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index ff7ac5d69e..3620896f34 100644 --- a/.editorconfig +++ b/.editorconfig @@ -169,6 +169,8 @@ dotnet_diagnostic.CA1828.severity = warning dotnet_diagnostic.CA1829.severity = warning #CA1847: Use string.Contains(char) instead of string.Contains(string) with single characters dotnet_diagnostic.CA1847.severity = warning +#CACA2211:Non-constant fields should not be visible +dotnet_diagnostic.CA2211.severity = error # Wrapping preferences csharp_wrap_before_ternary_opsigns = false From bd2e2d1e9b0175de4001765a7a102296f79339e6 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 3 Jan 2023 15:29:34 +0100 Subject: [PATCH 136/213] feat: Address Rule CA2211 --- .../ColorPalettes/FlatColorPalette.cs | 4 +++- .../ColorPalettes/FlatHalfColorPalette.cs | 4 +++- .../ColorPalettes/MaterialColorPalette.cs | 4 +++- .../ColorPalettes/MaterialHalfColorPalette.cs | 4 +++- src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridColumn.cs | 2 +- src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs | 2 +- 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs index 2cc5b99b2e..0188e61bec 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs @@ -289,8 +289,10 @@ namespace Avalonia.Controls }; // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png +#pragma warning disable CA2211 // Non-constant fields should not be visible protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); +#pragma warning restore CA2211 // Non-constant fields should not be visible + protected static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs index 2758124fae..1bffd66994 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs @@ -10,8 +10,10 @@ namespace Avalonia.Controls /// public class FlatHalfColorPalette : IColorPalette { +#pragma warning disable CA2211 // Non-constant fields should not be visible protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); +#pragma warning restore CA2211 // Non-constant fields should not be visible + protected static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs index d009926bc5..a9024bc18c 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs @@ -344,8 +344,10 @@ namespace Avalonia.Controls // See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors // This is a reduced palette for uniformity +#pragma warning disable CA2211 // Non-constant fields should not be visible protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); +#pragma warning restore CA2211 // Non-constant fields should not be visible + protected static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs index 01d44aa65d..371c94504b 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs @@ -10,8 +10,10 @@ namespace Avalonia.Controls /// public class MaterialHalfColorPalette : IColorPalette { +#pragma warning disable CA2211 // Non-constant fields should not be visible protected static Color[,]? _colorChart = null; - protected static object _colorChartMutex = new object(); +#pragma warning restore CA2211 // Non-constant fields should not be visible + protected static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index c308312398..8988be9b08 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -31,7 +31,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static StyledProperty IsThreeStateProperty = + public readonly static StyledProperty IsThreeStateProperty = CheckBox.IsThreeStateProperty.AddOwner(); /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 75101dc487..f6c503ac83 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -185,7 +185,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static StyledProperty IsVisibleProperty = + public static readonly StyledProperty IsVisibleProperty = Control.IsVisibleProperty.AddOwner(); /// diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs index 9fddb91231..99616e4cdf 100644 --- a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs +++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs @@ -8,6 +8,6 @@ /// /// Provides access to the internal for use in DevTools. /// - public static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; + public readonly static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; } } From 05d8b84ce3e4f30c6c5c643ce6cbf841c24121d5 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 3 Jan 2023 23:21:53 +0800 Subject: [PATCH 137/213] feat: Add GeometryConverter --- src/Avalonia.Base/Media/Geometry.cs | 33 ++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index 2019f54c70..b1fea53289 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -1,11 +1,14 @@ using System; using Avalonia.Platform; +using System.ComponentModel; +using System.Globalization; namespace Avalonia.Media { /// /// Defines a geometric shape. - /// + /// + [TypeConverter(typeof(GeometryConverter))] public abstract class Geometry : AvaloniaObject { /// @@ -199,4 +202,32 @@ namespace Avalonia.Media return new CombinedGeometry(combineMode, geometry1, geometry2, transform); } } + + public class GeometryConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + if (sourceType == typeof(string)) + { + return true; + } + return base.CanConvertFrom(context, sourceType); + } + + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is null) + { + throw GetConvertFromException(value); + } + string source = value as string; + + if (source != null) + { + return Geometry.Parse(source); + } + + return base.ConvertFrom(context, culture, value); + } + } } From 5846c3b1b6e9e13559e2221ac2332245783c5c47 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 15:41:08 +0000 Subject: [PATCH 138/213] Bump json5 from 1.0.1 to 1.0.2 in /src/Browser/Avalonia.Browser/webapp Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2. - [Release notes](https://github.com/json5/json5/releases) - [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md) - [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2) --- updated-dependencies: - dependency-name: json5 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../Avalonia.Browser/webapp/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Browser/Avalonia.Browser/webapp/package-lock.json b/src/Browser/Avalonia.Browser/webapp/package-lock.json index 06e94629d7..2d875e84db 100644 --- a/src/Browser/Avalonia.Browser/webapp/package-lock.json +++ b/src/Browser/Avalonia.Browser/webapp/package-lock.json @@ -2162,9 +2162,9 @@ "dev": true }, "node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -4699,9 +4699,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" From 6e1311c75d16547920b05812fcffa225da2d2893 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 4 Jan 2023 00:09:51 +0800 Subject: [PATCH 139/213] fix: fix nullable issue. --- src/Avalonia.Base/Media/Geometry.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index b1fea53289..e2b345df1a 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -214,13 +214,13 @@ namespace Avalonia.Media return base.CanConvertFrom(context, sourceType); } - public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { if (value is null) { throw GetConvertFromException(value); } - string source = value as string; + string? source = value as string; if (source != null) { From 5d1e5d97c2bf4d99e83a21c7e314761942e09ac8 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Tue, 3 Jan 2023 23:04:21 +0300 Subject: [PATCH 140/213] [Text] [Selection] fix double click selection on word end position --- src/Avalonia.Controls/SelectableTextBlock.cs | 6 ++- src/Avalonia.Controls/TextBox.cs | 6 ++- src/Avalonia.Controls/Utils/StringUtils.cs | 49 ++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/SelectableTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs index b343439f98..f4c4d54951 100644 --- a/src/Avalonia.Controls/SelectableTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -300,7 +300,11 @@ namespace Avalonia.Controls _wordSelectionStart = SelectionStart; - SelectionEnd = StringUtils.NextWord(text, index); + if (!StringUtils.IsEndOfWord(text, index)) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + break; case 3: _wordSelectionStart = -1; diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 1bdec878d9..9a2ee12dc3 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1475,7 +1475,11 @@ namespace Avalonia.Controls _wordSelectionStart = SelectionStart; - SelectionEnd = StringUtils.NextWord(text, index); + if (!StringUtils.IsEndOfWord(text, index)) + { + SelectionEnd = StringUtils.NextWord(text, index); + } + break; case 3: _wordSelectionStart = -1; diff --git a/src/Avalonia.Controls/Utils/StringUtils.cs b/src/Avalonia.Controls/Utils/StringUtils.cs index b2e56434b2..6c459d7985 100644 --- a/src/Avalonia.Controls/Utils/StringUtils.cs +++ b/src/Avalonia.Controls/Utils/StringUtils.cs @@ -67,6 +67,55 @@ namespace Avalonia.Controls.Utils } } + public static bool IsEndOfWord(string text, int index) + { + if (index >= text.Length) + { + return true; + } + + var codepoint = new Codepoint(text[index]); + + if (!codepoint.IsWhiteSpace) + { + return false; + } + // A 'word' starts with an AlphaNumeric or some punctuation symbols immediately + // preceeded by lwsp. + if (index > 0) + { + var nextCodePoint = new Codepoint(text[index + 1]); + + if (nextCodePoint.IsBreakChar) + { + return true; + } + } + + switch (codepoint.GeneralCategory) + { + case GeneralCategory.LowercaseLetter: + case GeneralCategory.TitlecaseLetter: + case GeneralCategory.UppercaseLetter: + case GeneralCategory.DecimalNumber: + case GeneralCategory.LetterNumber: + case GeneralCategory.OtherNumber: + case GeneralCategory.DashPunctuation: + case GeneralCategory.InitialPunctuation: + case GeneralCategory.OpenPunctuation: + case GeneralCategory.CurrencySymbol: + case GeneralCategory.MathSymbol: + return false; + + // TODO: How do you do this in .NET? + // case UnicodeCategory.OtherPunctuation: + // // words cannot start with '.', but they can start with '&' or '*' (for example) + // return g_unichar_break_type(buffer->text[index]) == G_UNICODE_BREAK_ALPHABETIC; + default: + return true; + } + } + public static int PreviousWord(string text, int cursor) { if (string.IsNullOrEmpty(text)) From 876fa088124941aeb60e81f00bdb2bf3a26b882a Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 4 Jan 2023 10:18:35 +1100 Subject: [PATCH 141/213] move to a consistent langversion of 11 --- Directory.Build.props | 1 + build/SharedVersion.props | 1 - src/Browser/Avalonia.Browser/Avalonia.Browser.csproj | 1 - src/tools/DevAnalyzers/DevAnalyzers.csproj | 1 - src/tools/DevGenerators/DevGenerators.csproj | 1 - tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj | 1 - .../Avalonia.Controls.DataGrid.UnitTests.csproj | 1 - .../Avalonia.Controls.UnitTests.csproj | 1 - .../Avalonia.Markup.Xaml.UnitTests.csproj | 1 - tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj | 1 - tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj | 1 - 11 files changed, 1 insertion(+), 10 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 42daa2df7f..c19a55e8ea 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,5 +7,6 @@ false false False + 11 diff --git a/build/SharedVersion.props b/build/SharedVersion.props index e9c3d65b41..eca3ba37b0 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -8,7 +8,6 @@ https://github.com/AvaloniaUI/Avalonia/ true $(NoWarn);CS1591 - preview MIT Icon.png Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly. diff --git a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj index 014d387cb2..2fc3cc885a 100644 --- a/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj +++ b/src/Browser/Avalonia.Browser/Avalonia.Browser.csproj @@ -1,7 +1,6 @@ net7.0 - preview enable true diff --git a/src/tools/DevAnalyzers/DevAnalyzers.csproj b/src/tools/DevAnalyzers/DevAnalyzers.csproj index 53e3e74e76..e5c2fc6cf6 100644 --- a/src/tools/DevAnalyzers/DevAnalyzers.csproj +++ b/src/tools/DevAnalyzers/DevAnalyzers.csproj @@ -2,7 +2,6 @@ netstandard2.0 - 10 enable diff --git a/src/tools/DevGenerators/DevGenerators.csproj b/src/tools/DevGenerators/DevGenerators.csproj index 069ff159fc..30da940514 100644 --- a/src/tools/DevGenerators/DevGenerators.csproj +++ b/src/tools/DevGenerators/DevGenerators.csproj @@ -4,7 +4,6 @@ netstandard2.0 enable false - 10 diff --git a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj index 0162f53f5e..4cdce8df26 100644 --- a/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj +++ b/tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj @@ -3,7 +3,6 @@ net6.0 Library true - latest diff --git a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj index eb1bf24d0c..20c2f711ad 100644 --- a/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj +++ b/tests/Avalonia.Controls.DataGrid.UnitTests/Avalonia.Controls.DataGrid.UnitTests.csproj @@ -1,7 +1,6 @@ net6.0 - latest Library true diff --git a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj index 12eb290fde..471f19f948 100644 --- a/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj +++ b/tests/Avalonia.Controls.UnitTests/Avalonia.Controls.UnitTests.csproj @@ -1,7 +1,6 @@  net6.0 - latest Library true diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index 6dce5eaab5..fa4957c24c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -3,7 +3,6 @@ net6.0;net47 Library true - latest diff --git a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj index ccde66a50e..ea91b8c196 100644 --- a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj +++ b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj @@ -1,7 +1,6 @@  net6.0 - latest diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index cb6884cad8..b9c2a619d9 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -1,7 +1,6 @@  netstandard2.0 - latest false Library false From 2e68b148e3f797e22f43c80e33b6601451564bb7 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 4 Jan 2023 10:21:02 +1100 Subject: [PATCH 142/213] move sdk to 7.0.101 --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index a9318b212f..a3e251852f 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.100", + "version": "7.0.101", "rollForward": "latestFeature" }, "msbuild-sdks": { From 9cc8381d3e544e2a9026669fda0eb7d5e20291b0 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 4 Jan 2023 10:34:34 +1100 Subject: [PATCH 143/213] fix minor typos * adversly => adversely * visiblility => visibility --- samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs | 2 +- src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs index 11f504f4db..7520dabf37 100644 --- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs @@ -17,7 +17,7 @@ namespace ControlCatalog.Pages this.Get("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " + "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " + "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " + - "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden."; + "12 - hour or 24 - hour clock and visibility of AM / PM is dynamically set based on user time settings, or can be overridden."; } diff --git a/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs b/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs index 8ecd9119f6..ecd6154e0b 100644 --- a/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs +++ b/src/tools/DevAnalyzers/GenericVirtualAnalyzer.cs @@ -16,7 +16,7 @@ public class GenericVirtualAnalyzer : DiagnosticAnalyzer "Performance", DiagnosticSeverity.Warning, isEnabledByDefault: true, - description: "Generic virtual methods affect JIT startup time adversly and should be avoided."); + description: "Generic virtual methods affect JIT startup time adversely and should be avoided."); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); From 9de36fc19deed18f32c33e24221c595a7166aca1 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 4 Jan 2023 10:37:45 +1100 Subject: [PATCH 144/213] fix Committed typo Commited => Committed --- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 2 +- .../Composition/Server/ServerCompositionDrawListVisual.cs | 4 ++-- .../Composition/Server/ServerCustomCompositionVisual.cs | 6 +++--- .../Rendering/Composition/Server/ServerList.cs | 4 ++-- .../Rendering/Composition/Server/ServerObject.cs | 8 ++++---- .../Rendering/Composition/Transport/Batch.cs | 2 +- src/Avalonia.Controls/BorderVisual.cs | 4 ++-- src/tools/DevGenerators/CompositionGenerator/Generator.cs | 6 +++--- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index b4817bfe9a..7fc5487171 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -111,7 +111,7 @@ namespace Avalonia.Rendering.Composition } } - batch.CommitedAt = Server.Clock.Elapsed; + batch.CommittedAt = Server.Clock.Elapsed; _server.EnqueueBatch(batch); lock (_pendingBatchLock) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs index 8dc088fed1..aebe3a5cdd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs @@ -48,7 +48,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua } } - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { if (reader.Read() == 1) { @@ -56,7 +56,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua _renderCommands = reader.ReadObject(); _contentBounds = null; } - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); } protected override void RenderCore(CompositorDrawingContextProxy canvas, Rect currentTransformedClip) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs index 227df87a87..74889c9bfe 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCustomCompositionVisual.cs @@ -16,9 +16,9 @@ internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, _handler.Attach(this); } - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); var count = reader.Read(); for (var c = 0; c < count; c++) { @@ -79,4 +79,4 @@ internal class ServerCompositionCustomVisual : ServerCompositionContainerVisual, ?.Log(_handler, $"Exception in {_handler.GetType().Name}.{nameof(CompositionCustomVisualHandler.OnRender)} {{0}}", e); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs index 08a3fdce22..2500358866 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerList.cs @@ -14,7 +14,7 @@ namespace Avalonia.Rendering.Composition.Server { public List List { get; } = new List(); - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { if (reader.Read() == 1) { @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Server for (var c = 0; c < count; c++) List.Add(reader.ReadObject()); } - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); } public override long LastChangedBy diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs index 257d3b29a2..31cb16ec20 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerObject.cs @@ -104,13 +104,13 @@ namespace Avalonia.Rendering.Composition.Server } protected void SetAnimatedValue(CompositionProperty prop, ref T field, - TimeSpan commitedAt, IAnimationInstance animation) where T : struct + TimeSpan committedAt, IAnimationInstance animation) where T : struct { if (IsActive && _animations.TryGetValue(prop, out var oldAnimation)) oldAnimation.Deactivate(); _animations[prop] = animation; - animation.Initialize(commitedAt, ExpressionVariant.Create(field), prop); + animation.Initialize(committedAt, ExpressionVariant.Create(field), prop); if(IsActive) animation.Activate(); @@ -165,7 +165,7 @@ namespace Avalonia.Rendering.Composition.Server public virtual CompositionProperty? GetCompositionProperty(string fieldName) => null; - protected virtual void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected virtual void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { if (this is IDisposable disp && reader.Read() == 1) @@ -174,7 +174,7 @@ namespace Avalonia.Rendering.Composition.Server public void DeserializeChanges(BatchStreamReader reader, Batch batch) { - DeserializeChangesCore(reader, batch.CommitedAt); + DeserializeChangesCore(reader, batch.CommittedAt); ValuesInvalidated(); ItselfLastChangedBy = batch.SequenceId; } diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs index d3e3664f84..803ea809c0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs +++ b/src/Avalonia.Base/Rendering/Composition/Transport/Batch.cs @@ -29,7 +29,7 @@ namespace Avalonia.Rendering.Composition.Transport public BatchStreamData Changes { get; private set; } - public TimeSpan CommitedAt { get; set; } + public TimeSpan CommittedAt { get; set; } public void Complete() { diff --git a/src/Avalonia.Controls/BorderVisual.cs b/src/Avalonia.Controls/BorderVisual.cs index 18448edac4..b0e5c30e2f 100644 --- a/src/Avalonia.Controls/BorderVisual.cs +++ b/src/Avalonia.Controls/BorderVisual.cs @@ -63,9 +63,9 @@ class CompositionBorderVisual : CompositionDrawListVisual } - protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt) + protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt) { - base.DeserializeChangesCore(reader, commitedAt); + base.DeserializeChangesCore(reader, committedAt); if (reader.Read()) _cornerRadius = reader.Read(); } diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.cs index f8d5ad826e..3b5d3d8c3f 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.cs @@ -257,7 +257,7 @@ namespace Avalonia.SourceGenerator.CompositionGenerator if (cl.Properties.Count > 0) { server = server.AddMembers(((MethodDeclarationSyntax)ParseMemberDeclaration( - $"protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan commitedAt){{}}") + $"protected override void DeserializeChangesCore(BatchStreamReader reader, TimeSpan committedAt){{}}") !) .WithBody(ApplyDeserializeChangesEpilogue(deserializeMethodBody, cl))); server = server.AddMembers(MethodDeclaration(ParseTypeName("void"), "OnFieldsDeserialized") @@ -481,7 +481,7 @@ return; private static BlockSyntax DeserializeChangesPrologue(GClass cl) { return Block(ParseStatement($@" -base.DeserializeChangesCore(reader, commitedAt); +base.DeserializeChangesCore(reader, committedAt); DeserializeChangesExtra(reader); var changed = reader.Read<{ChangedFieldsTypeName(cl)}>(); ")); @@ -500,7 +500,7 @@ var changed = reader.Read<{ChangedFieldsTypeName(cl)}>(); { code = $@" if((changed & {changedFieldsType}.{prop.Name}Animated) == {changedFieldsType}.{prop.Name}Animated) - SetAnimatedValue({CompositionPropertyField(prop)}, ref {PropertyBackingFieldName(prop)}, commitedAt, reader.ReadObject()); + SetAnimatedValue({CompositionPropertyField(prop)}, ref {PropertyBackingFieldName(prop)}, committedAt, reader.ReadObject()); else "; } From ba5e44a4dfdda506fb34e20e697e84535bf91d5d Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 4 Jan 2023 10:51:33 +1100 Subject: [PATCH 145/213] 7.0.101 in pipelines --- azure-pipelines-integrationtests.yml | 8 ++++---- azure-pipelines.yml | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml index 43253ac6be..02e8bba43f 100644 --- a/azure-pipelines-integrationtests.yml +++ b/azure-pipelines-integrationtests.yml @@ -18,9 +18,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - script: system_profiler SPDisplaysDataType |grep Resolution @@ -56,9 +56,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: Windows Application Driver@0 inputs: diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a3bbc33418..fb04d4dd25 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -35,9 +35,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: CmdLine@2 displayName: 'Install Workloads' @@ -72,9 +72,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: CmdLine@2 displayName: 'Install Workloads' @@ -143,9 +143,9 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100' + displayName: 'Use .NET Core SDK 7.0.101' inputs: - version: 7.0.100 + version: 7.0.101 - task: CmdLine@2 displayName: 'Install Workloads' From 773f24dfc672eeb1bde3e0cdac73578901ec6636 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 4 Jan 2023 10:53:42 +1100 Subject: [PATCH 146/213] use SDK 6.0.404 --- azure-pipelines-integrationtests.yml | 8 ++++---- azure-pipelines.yml | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml index 43253ac6be..33e65405d0 100644 --- a/azure-pipelines-integrationtests.yml +++ b/azure-pipelines-integrationtests.yml @@ -13,9 +13,9 @@ jobs: steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100' @@ -51,9 +51,9 @@ jobs: steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a3bbc33418..c61b40de6c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,9 +30,9 @@ jobs: vmImage: 'ubuntu-20.04' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0' @@ -67,9 +67,9 @@ jobs: vmImage: 'macos-12' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100' @@ -138,9 +138,9 @@ jobs: SolutionDir: '$(Build.SourcesDirectory)' steps: - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 6.0.401' + displayName: 'Use .NET Core SDK 6.0.404' inputs: - version: 6.0.401 + version: 6.0.404 - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100' From 8f745ba6fcfe975d62b8b3c75082d11db6dc4f9a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 4 Jan 2023 09:23:36 +0100 Subject: [PATCH 147/213] fix: Address Review --- .../ColorPalettes/FlatColorPalette.cs | 6 ++---- .../ColorPalettes/FlatHalfColorPalette.cs | 6 ++---- .../ColorPalettes/MaterialColorPalette.cs | 6 ++---- .../ColorPalettes/MaterialHalfColorPalette.cs | 6 ++---- src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs | 2 +- src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs | 2 +- 6 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs index 0188e61bec..aaf272c6d2 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatColorPalette.cs @@ -289,10 +289,8 @@ namespace Avalonia.Controls }; // See: https://htmlcolorcodes.com/assets/downloads/flat-design-colors/flat-design-color-chart.png -#pragma warning disable CA2211 // Non-constant fields should not be visible - protected static Color[,]? _colorChart = null; -#pragma warning restore CA2211 // Non-constant fields should not be visible - protected static readonly object _colorChartMutex = new(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs index 1bffd66994..4a30fb6cb4 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/FlatHalfColorPalette.cs @@ -10,10 +10,8 @@ namespace Avalonia.Controls /// public class FlatHalfColorPalette : IColorPalette { -#pragma warning disable CA2211 // Non-constant fields should not be visible - protected static Color[,]? _colorChart = null; -#pragma warning restore CA2211 // Non-constant fields should not be visible - protected static readonly object _colorChartMutex = new(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs index a9024bc18c..d4b904163c 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs @@ -344,10 +344,8 @@ namespace Avalonia.Controls // See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors // This is a reduced palette for uniformity -#pragma warning disable CA2211 // Non-constant fields should not be visible - protected static Color[,]? _colorChart = null; -#pragma warning restore CA2211 // Non-constant fields should not be visible - protected static readonly object _colorChartMutex = new(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs index 371c94504b..4432642675 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialHalfColorPalette.cs @@ -10,10 +10,8 @@ namespace Avalonia.Controls /// public class MaterialHalfColorPalette : IColorPalette { -#pragma warning disable CA2211 // Non-constant fields should not be visible - protected static Color[,]? _colorChart = null; -#pragma warning restore CA2211 // Non-constant fields should not be visible - protected static readonly object _colorChartMutex = new(); + private static Color[,]? _colorChart = null; + private static readonly object _colorChartMutex = new(); /// /// Initializes all color chart colors. diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index 8988be9b08..39c1e4c118 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -31,7 +31,7 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public readonly static StyledProperty IsThreeStateProperty = + public static readonly StyledProperty IsThreeStateProperty = CheckBox.IsThreeStateProperty.AddOwner(); /// diff --git a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs index 99616e4cdf..343b628fb9 100644 --- a/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs +++ b/src/Avalonia.Controls/Diagnostics/ToolTipDiagnostics.cs @@ -8,6 +8,6 @@ /// /// Provides access to the internal for use in DevTools. /// - public readonly static AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; + public static readonly AvaloniaProperty ToolTipProperty = ToolTip.ToolTipProperty; } } From 3eebc5498c6b91267a421528f4d5e0103191d88f Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 5 Jan 2023 16:10:44 +1100 Subject: [PATCH 148/213] remove MSBuild.Sdk.Extras and AggregatePackage.NuGet.Sdk --- global.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/global.json b/global.json index a3e251852f..e4a0cc20c2 100644 --- a/global.json +++ b/global.json @@ -4,8 +4,6 @@ "rollForward": "latestFeature" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "1.0.43", - "MSBuild.Sdk.Extras": "3.0.22", - "AggregatePackage.NuGet.Sdk" : "0.1.12" + "Microsoft.Build.Traversal": "1.0.43" } } From e049313e7a563b3ab65ad679980309aca34af14a Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 5 Jan 2023 20:50:34 +1100 Subject: [PATCH 149/213] Microsoft.Build.Traversal 3.2.0 --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index e4a0cc20c2..8536535b51 100644 --- a/global.json +++ b/global.json @@ -4,6 +4,6 @@ "rollForward": "latestFeature" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "1.0.43" + "Microsoft.Build.Traversal": "3.2.0" } } From 91a7a6bad121e64159ffa8dc3d1dfc7b6457ea44 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 5 Jan 2023 21:24:19 +1100 Subject: [PATCH 150/213] remove _notificationManager it is not assigned or used --- samples/ControlCatalog/ViewModels/MainWindowViewModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 3bb4202166..47c6f70714 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -12,8 +12,6 @@ namespace ControlCatalog.ViewModels { class MainWindowViewModel : ViewModelBase { - private IManagedNotificationManager _notificationManager; - private bool _isMenuItemChecked = true; private WindowState _windowState; private WindowState[] _windowStates = Array.Empty(); From f84cdbeda946113ff74a211fa23cf82f6f4feeac Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 5 Jan 2023 21:37:03 +1100 Subject: [PATCH 151/213] respect success in LoadGroupSre --- .../AvaloniaXamlIlRuntimeCompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs index 2bb91e8a32..e3d154ffdd 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs @@ -179,7 +179,7 @@ namespace Avalonia.Markup.Xaml.XamlIl } finally { - if( _sreCanSave) + if(!success && _sreCanSave) DumpRuntimeCompilationResults(); } } From dba476c11c0f3c92be8b3356c6a02ea393c52cc2 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 5 Jan 2023 22:19:57 +1100 Subject: [PATCH 152/213] fix more typos --- samples/ControlCatalog/Models/StateData.cs | 6 +++--- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 4 ++-- .../AvaloniaXamlIlSelectorTransformer.cs | 6 +++--- .../AvaloniaObjectTests_SetValue.cs | 8 ++++---- .../Core/ExpressionObserverTests_DataValidation.cs | 2 +- .../Core/ExpressionObserverTests_ExpressionTree.cs | 2 +- tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs | 2 +- tests/Avalonia.Base.UnitTests/VisualTests.cs | 4 ++-- tests/Avalonia.Controls.UnitTests/WindowTests.cs | 12 ++++++------ .../Xaml/BasicTests.cs | 4 ++-- .../Xaml/ControlTemplateTests.cs | 6 +++--- .../Xaml/StyleTests.cs | 4 ++-- .../Avalonia.RenderTests/Controls/TextBlockTests.cs | 4 ++-- .../Media/TextFormatting/TextLayoutTests.cs | 2 +- tests/Avalonia.RenderTests/OpacityMaskTests.cs | 2 +- 15 files changed, 34 insertions(+), 34 deletions(-) diff --git a/samples/ControlCatalog/Models/StateData.cs b/samples/ControlCatalog/Models/StateData.cs index bd6d186252..a7ea655cd2 100644 --- a/samples/ControlCatalog/Models/StateData.cs +++ b/samples/ControlCatalog/Models/StateData.cs @@ -6,10 +6,10 @@ public class StateData public string Abbreviation { get; private set; } public string Capital { get; private set; } - public StateData(string name, string abbreviatoin, string capital) + public StateData(string name, string abbreviation, string capital) { Name = name; - Abbreviation = abbreviatoin; + Abbreviation = abbreviation; Capital = capital; } @@ -17,4 +17,4 @@ public class StateData { return Name; } -} \ No newline at end of file +} diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 657e324010..4466b938cf 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -25,9 +25,9 @@ namespace Avalonia.FreeDesktop return new DBusMenuExporterImpl(DBusHelper.Connection, xid); } - public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path, Connection currentConection) + public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path, Connection currentConnection) { - return new DBusMenuExporterImpl(currentConection, path); + return new DBusMenuExporterImpl(currentConnection, path); } public static ObjectPath GenerateDBusMenuObjPath => "/net/avaloniaui/dbusmenu/" diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index ba0da4fbe0..078e23bc02 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -124,7 +124,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers $"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}", node); - result = new XamlIlAttacchedPropertyEqualsSelector(result, targetPropertyField, typedValue); + result = new XamlIlAttachedPropertyEqualsSelector(result, targetPropertyField, typedValue); break; } case SelectorGrammar.ChildSyntax child: @@ -414,9 +414,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } - class XamlIlAttacchedPropertyEqualsSelector : XamlIlSelectorNode + class XamlIlAttachedPropertyEqualsSelector : XamlIlSelectorNode { - public XamlIlAttacchedPropertyEqualsSelector(XamlIlSelectorNode previous, + public XamlIlAttachedPropertyEqualsSelector(XamlIlSelectorNode previous, IXamlField propertyFiled, IXamlAstValueNode value) : base(previous) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index f878977cf2..99c4ac3d86 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -227,7 +227,7 @@ namespace Avalonia.Base.UnitTests { Class2 target = new Class2(); - target.SetValue((AvaloniaProperty)Class2.FlobProperty, new ImplictDouble(4)); + target.SetValue((AvaloniaProperty)Class2.FlobProperty, new ImplicitDouble(4)); var value = target.GetValue(Class2.FlobProperty); Assert.IsType(value); @@ -393,16 +393,16 @@ namespace Avalonia.Base.UnitTests AvaloniaProperty.RegisterAttached("Attached"); } - private class ImplictDouble + private class ImplicitDouble { - public ImplictDouble(double value) + public ImplicitDouble(double value) { Value = value; } public double Value { get; } - public static implicit operator double (ImplictDouble v) + public static implicit operator double (ImplicitDouble v) { return v.Value; } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs index 43192584af..2a8bbaae79 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs @@ -49,7 +49,7 @@ namespace Avalonia.Base.UnitTests.Data.Core ////} [Fact] - public void Indei_Validation_Does_Not_Subscribe_When_DataValidatation_Not_Enabled() + public void Indei_Validation_Does_Not_Subscribe_When_DataValidation_Not_Enabled() { var data = new IndeiTest { MustBePositive = 5 }; var observer = ExpressionObserver.Create(data, o => o.MustBePositive, false); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs index 9b587d7679..6cd757331b 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_ExpressionTree.cs @@ -42,7 +42,7 @@ namespace Avalonia.Base.UnitTests.Data.Core } [Fact] - public void Property_Acccess_Expression_Can_Set_Property() + public void Property_Access_Expression_Can_Set_Property() { var data = new Class1(); var target = ExpressionObserver.Create(data, o => o.Foo); diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 84ee35ba61..d466f6a4a5 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -506,7 +506,7 @@ namespace Avalonia.Base.UnitTests.Input } [Fact] - public void Scrolling_Should_Start_After_Start_Distance_Is_Exceded() + public void Scrolling_Should_Start_After_Start_Distance_Is_Exceeded() { Border border = new Border() { diff --git a/tests/Avalonia.Base.UnitTests/VisualTests.cs b/tests/Avalonia.Base.UnitTests/VisualTests.cs index 973893af2d..fb214a6b34 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTests.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTests.cs @@ -126,7 +126,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Root_Should_Retun_Self_As_VisualRoot() + public void Root_Should_Return_Self_As_VisualRoot() { var root = new TestRoot(); @@ -134,7 +134,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Descendants_Should_RetunVisualRoot() + public void Descendants_Should_ReturnVisualRoot() { var root = new TestRoot(); var child1 = new Decorator(); diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 9dc0cb95bd..ca245005c2 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -140,7 +140,7 @@ namespace Avalonia.Controls.UnitTests [Theory] [InlineData(true)] [InlineData(false)] - public void Child_windows_should_be_closed_before_parent(bool programaticClose) + public void Child_windows_should_be_closed_before_parent(bool programmaticClose) { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -156,7 +156,7 @@ namespace Avalonia.Controls.UnitTests window.Closing += (sender, e) => { Assert.Equal(WindowCloseReason.WindowClosing, e.CloseReason); - Assert.Equal(programaticClose, e.IsProgrammatic); + Assert.Equal(programmaticClose, e.IsProgrammatic); count++; windowClosing = count; }; @@ -164,7 +164,7 @@ namespace Avalonia.Controls.UnitTests child.Closing += (sender, e) => { Assert.Equal(WindowCloseReason.OwnerWindowClosing, e.CloseReason); - Assert.Equal(programaticClose, e.IsProgrammatic); + Assert.Equal(programmaticClose, e.IsProgrammatic); count++; childClosing = count; }; @@ -184,7 +184,7 @@ namespace Avalonia.Controls.UnitTests window.Show(); child.Show(window); - if (programaticClose) + if (programmaticClose) { window.Close(); } @@ -205,7 +205,7 @@ namespace Avalonia.Controls.UnitTests [Theory] [InlineData(true)] [InlineData(false)] - public void Child_windows_must_not_close_before_parent_has_chance_to_Cancel_OSCloseButton(bool programaticClose) + public void Child_windows_must_not_close_before_parent_has_chance_to_Cancel_OSCloseButton(bool programmaticClose) { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -246,7 +246,7 @@ namespace Avalonia.Controls.UnitTests window.Show(); child.Show(window); - if (programaticClose) + if (programmaticClose) { window.Close(); } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs index 18a6dd9803..0cdc9ee3b1 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs @@ -59,7 +59,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void Attached_Property_Is_Set_On_Control_Outside_Avalonia_Namspace() + public void Attached_Property_Is_Set_On_Control_Outside_Avalonia_Namespace() { // Test for issue #1548 var xaml = @@ -729,7 +729,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void DeferedXamlLoader_Should_Preserve_NamespacesContext() + public void DeferredXamlLoader_Should_Preserve_NamespacesContext() { var xaml = @" LineDashStyleProperty = - AvaloniaProperty.Register(nameof(LineDashStyle)); + AvaloniaProperty.Register(nameof(LineDashStyle)); public DashStyle LineDashStyle { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index 70a5295008..ae1b12572e 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -272,7 +272,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void Style_Can_Use_NthChild_Selector_After_Reoder() + public void Style_Can_Use_NthChild_Selector_After_Reorder() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { @@ -311,7 +311,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml } [Fact] - public void Style_Can_Use_NthLastChild_Selector_After_Reoder() + public void Style_Can_Use_NthLastChild_Selector_After_Reorder() { using (UnitTestApplication.Start(TestServices.StyledWindow)) { diff --git a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs index bddde3c3c6..c11bd2b816 100644 --- a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs +++ b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs @@ -45,7 +45,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls [Win32Fact("Has text")] public async Task RestrictedHeight_VerticalAlign() { - Control text(VerticalAlignment verticalAlingnment, bool clip = true, bool restrictHeight = true) + Control text(VerticalAlignment verticalAlignment, bool clip = true, bool restrictHeight = true) { return new Border() { @@ -62,7 +62,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls FontSize = 24, Foreground = Brushes.Black, Text = "L", - VerticalAlignment = verticalAlingnment, + VerticalAlignment = verticalAlignment, ClipToBounds = clip } }; diff --git a/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs index f921d9fa64..6f47aa58d8 100644 --- a/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.RenderTests/Media/TextFormatting/TextLayoutTests.cs @@ -186,7 +186,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media [InlineData("x", 0, 200, 200 - 7.20, 0, 7.20, FontSizeHeight)] [InlineData(stringword, 0, 200, 171.20, 0, 7.20, FontSizeHeight)] [InlineData(stringword, 3, 200, 200 - 7.20, 0, 7.20, FontSizeHeight)] - public void Should_HitTestPosition_RigthAlign_Correctly( + public void Should_HitTestPosition_RightAlign_Correctly( string input, int index, double widthConstraint, double x, double y, double width, double height) { diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs index 2f01b03db6..6ce7e72df1 100644 --- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs +++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs @@ -57,7 +57,7 @@ namespace Avalonia.Direct2D1.RenderTests } [Fact] - public async Task RenderTansform_Applies_To_Opacity_Mask() + public async Task RenderTransform_Applies_To_Opacity_Mask() { var target = new Canvas { From 5fef8af151e7673264f20a41e96c97a2a4846a68 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Thu, 5 Jan 2023 18:31:02 +0300 Subject: [PATCH 153/213] [Text] [Rename] ShapeableTextCharacters to UnshapedTextRun and ShapedTextCharacters to ShapedTextRun --- .../TextFormatting/InterWordJustification.cs | 2 +- ...apedTextCharacters.cs => ShapedTextRun.cs} | 12 +++--- .../Media/TextFormatting/TextCharacters.cs | 18 ++++----- .../TextFormatting/TextEllipsisHelper.cs | 2 +- .../Media/TextFormatting/TextFormatterImpl.cs | 28 ++++++------- .../TextLeadingPrefixCharacterEllipsis.cs | 4 +- .../Media/TextFormatting/TextLineImpl.cs | 40 +++++++++---------- ...leTextCharacters.cs => UnshapedTextRun.cs} | 16 ++++---- .../TextFormatting/TextFormatterTests.cs | 4 +- .../Media/TextFormatting/TextLayoutTests.cs | 28 ++++++------- .../Media/TextFormatting/TextLineTests.cs | 30 +++++++------- 11 files changed, 92 insertions(+), 92 deletions(-) rename src/Avalonia.Base/Media/TextFormatting/{ShapedTextCharacters.cs => ShapedTextRun.cs} (92%) rename src/Avalonia.Base/Media/TextFormatting/{ShapeableTextCharacters.cs => UnshapedTextRun.cs} (61%) diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs index 3c3a46c209..21e8ce089a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs +++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs @@ -91,7 +91,7 @@ namespace Avalonia.Media.TextFormatting continue; } - if (textRun is ShapedTextCharacters shapedText) + if (textRun is ShapedTextRun shapedText) { var glyphRun = shapedText.GlyphRun; var shapedBuffer = shapedText.ShapedBuffer; diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs similarity index 92% rename from src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs rename to src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index 3035eb7b18..3149bc2cda 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -6,11 +6,11 @@ namespace Avalonia.Media.TextFormatting /// /// A text run that holds shaped characters. /// - public sealed class ShapedTextCharacters : DrawableTextRun + public sealed class ShapedTextRun : DrawableTextRun { private GlyphRun? _glyphRun; - public ShapedTextCharacters(ShapedBuffer shapedBuffer, TextRunProperties properties) + public ShapedTextRun(ShapedBuffer shapedBuffer, TextRunProperties properties) { ShapedBuffer = shapedBuffer; CharacterBufferReference = shapedBuffer.CharacterBufferRange.CharacterBufferReference; @@ -155,7 +155,7 @@ namespace Avalonia.Media.TextFormatting return length > 0; } - internal SplitResult Split(int length) + internal SplitResult Split(int length) { if (IsReversed) { @@ -171,7 +171,7 @@ namespace Avalonia.Media.TextFormatting var splitBuffer = ShapedBuffer.Split(length); - var first = new ShapedTextCharacters(splitBuffer.First, Properties); + var first = new ShapedTextRun(splitBuffer.First, Properties); #if DEBUG @@ -182,9 +182,9 @@ namespace Avalonia.Media.TextFormatting #endif - var second = new ShapedTextCharacters(splitBuffer.Second!, Properties); + var second = new ShapedTextRun(splitBuffer.Second!, Properties); - return new SplitResult(first, second); + return new SplitResult(first, second); } internal GlyphRun CreateGlyphRun() diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 0be753bd04..db035b8750 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -91,12 +91,12 @@ namespace Avalonia.Media.TextFormatting public override TextRunProperties Properties { get; } /// - /// Gets a list of . + /// Gets a list of . /// /// The shapeable text characters. - internal IReadOnlyList GetShapeableCharacters(CharacterBufferRange characterBufferRange, sbyte biDiLevel, ref TextRunProperties? previousProperties) + internal IReadOnlyList GetShapeableCharacters(CharacterBufferRange characterBufferRange, sbyte biDiLevel, ref TextRunProperties? previousProperties) { - var shapeableCharacters = new List(2); + var shapeableCharacters = new List(2); while (characterBufferRange.Length > 0) { @@ -120,7 +120,7 @@ namespace Avalonia.Media.TextFormatting /// The bidi level of the run. /// /// A list of shapeable text runs. - private static ShapeableTextCharacters CreateShapeableRun(CharacterBufferRange characterBufferRange, + private static UnshapedTextRun CreateShapeableRun(CharacterBufferRange characterBufferRange, TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties) { var defaultTypeface = defaultProperties.Typeface; @@ -133,12 +133,12 @@ namespace Avalonia.Media.TextFormatting { if (TryGetShapeableLength(characterBufferRange, previousTypeface.Value, null, out var fallbackCount, out _)) { - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, fallbackCount, + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, fallbackCount, defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); } } - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), biDiLevel); } @@ -146,7 +146,7 @@ namespace Avalonia.Media.TextFormatting { if (TryGetShapeableLength(characterBufferRange, previousTypeface.Value, defaultTypeface, out count, out _)) { - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); } } @@ -176,7 +176,7 @@ namespace Avalonia.Media.TextFormatting if (matchFound && TryGetShapeableLength(characterBufferRange, currentTypeface, defaultTypeface, out count, out _)) { //Fallback found - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties.WithTypeface(currentTypeface), biDiLevel); } @@ -199,7 +199,7 @@ namespace Avalonia.Media.TextFormatting count += grapheme.Text.Length; } - return new ShapeableTextCharacters(characterBufferRange.CharacterBufferReference, count, defaultProperties, biDiLevel); + return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties, biDiLevel); } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs index a1b8985b43..086ea85d97 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextEllipsisHelper.cs @@ -31,7 +31,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { currentWidth += shapedRun.Size.Width; diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 93eb4811b9..ef2abdfea0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -124,7 +124,7 @@ namespace Avalonia.Media.TextFormatting var second = new List(secondCount); - if (currentRun is ShapedTextCharacters shapedTextCharacters) + if (currentRun is ShapedTextRun shapedTextCharacters) { var split = shapedTextCharacters.Split(length - currentLength); @@ -206,16 +206,16 @@ namespace Avalonia.Media.TextFormatting break; } - case ShapeableTextCharacters shapeableRun: + case UnshapedTextRun shapeableRun: { - var groupedRuns = new List(2) { shapeableRun }; + var groupedRuns = new List(2) { shapeableRun }; var characterBufferReference = currentRun.CharacterBufferReference; var length = currentRun.Length; var offsetToFirstCharacter = characterBufferReference.OffsetToFirstChar; while (index + 1 < processedRuns.Count) { - if (processedRuns[index + 1] is not ShapeableTextCharacters nextRun) + if (processedRuns[index + 1] is not UnshapedTextRun nextRun) { break; } @@ -258,10 +258,10 @@ namespace Avalonia.Media.TextFormatting return drawableTextRuns; } - private static IReadOnlyList ShapeTogether( - IReadOnlyList textRuns, CharacterBufferReference text, int length, TextShaperOptions options) + private static IReadOnlyList ShapeTogether( + IReadOnlyList textRuns, CharacterBufferReference text, int length, TextShaperOptions options) { - var shapedRuns = new List(textRuns.Count); + var shapedRuns = new List(textRuns.Count); var shapedBuffer = TextShaper.Current.ShapeText(text, length, options); @@ -271,7 +271,7 @@ namespace Avalonia.Media.TextFormatting var splitResult = shapedBuffer.Split(currentRun.Length); - shapedRuns.Add(new ShapedTextCharacters(splitResult.First, currentRun.Properties)); + shapedRuns.Add(new ShapedTextRun(splitResult.First, currentRun.Properties)); shapedBuffer = splitResult.Second!; } @@ -280,9 +280,9 @@ namespace Avalonia.Media.TextFormatting } /// - /// Coalesces ranges of the same bidi level to form + /// Coalesces ranges of the same bidi level to form /// - /// The text characters to form from. + /// The text characters to form from. /// The bidi levels. /// private static IEnumerable> CoalesceLevels(IReadOnlyList textCharacters, ArraySlice levels) @@ -474,7 +474,7 @@ namespace Avalonia.Media.TextFormatting { switch (currentRun) { - case ShapedTextCharacters shapedTextCharacters: + case ShapedTextRun shapedTextCharacters: { if(shapedTextCharacters.ShapedBuffer.Length > 0) { @@ -538,7 +538,7 @@ namespace Avalonia.Media.TextFormatting var shapedBuffer = new ShapedBuffer(characterBufferRange, glyphInfos, glyphTypeface, properties.FontRenderingEmSize, (sbyte)flowDirection); - var textRuns = new List { new ShapedTextCharacters(shapedBuffer, properties) }; + var textRuns = new List { new ShapedTextRun(shapedBuffer, properties) }; return new TextLineImpl(textRuns, firstTextSourceIndex, 0, paragraphWidth, paragraphProperties, flowDirection).FinalizeLine(); } @@ -744,7 +744,7 @@ namespace Avalonia.Media.TextFormatting /// /// The shaped symbol. /// - internal static ShapedTextCharacters CreateSymbol(TextRun textRun, FlowDirection flowDirection) + internal static ShapedTextRun CreateSymbol(TextRun textRun, FlowDirection flowDirection) { var textShaper = TextShaper.Current; @@ -760,7 +760,7 @@ namespace Avalonia.Media.TextFormatting var shapedBuffer = textShaper.ShapeText(characterBuffer, textRun.Length, shaperOptions); - return new ShapedTextCharacters(shapedBuffer, textRun.Properties); + return new ShapedTextRun(shapedBuffer, textRun.Properties); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index 2752af8f0c..7b80d5ce40 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -65,7 +65,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { currentWidth += currentRun.Size.Width; @@ -118,7 +118,7 @@ namespace Avalonia.Media.TextFormatting switch (run) { - case ShapedTextCharacters endShapedRun: + case ShapedTextRun endShapedRun: { if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth, out var suffixCount, out var suffixWidth)) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index d893468052..5fb1171221 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -192,14 +192,14 @@ namespace Avalonia.Media.TextFormatting { var currentRun = _textRuns[i]; - if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) + if (currentRun is ShapedTextRun shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) { var rightToLeftIndex = i; currentPosition += currentRun.Length; while (rightToLeftIndex + 1 <= _textRuns.Count - 1) { - var nextShaped = _textRuns[++rightToLeftIndex] as ShapedTextCharacters; + var nextShaped = _textRuns[++rightToLeftIndex] as ShapedTextRun; if (nextShaped == null || nextShaped.ShapedBuffer.IsLeftToRight) { @@ -255,7 +255,7 @@ namespace Avalonia.Media.TextFormatting switch (run) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _); @@ -303,7 +303,7 @@ namespace Avalonia.Media.TextFormatting { var currentRun = _textRuns[index]; - if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) + if (currentRun is ShapedTextRun shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight) { var i = index; @@ -313,7 +313,7 @@ namespace Avalonia.Media.TextFormatting { var nextRun = _textRuns[i + 1]; - if (nextRun is ShapedTextCharacters nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) + if (nextRun is ShapedTextRun nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight) { i++; @@ -407,7 +407,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedTextCharacters: + case ShapedTextRun shapedTextCharacters: { currentGlyphRun = shapedTextCharacters.GlyphRun; @@ -476,7 +476,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(characterHit); break; @@ -550,7 +550,7 @@ namespace Avalonia.Media.TextFormatting double combinedWidth; - if (currentRun is ShapedTextCharacters currentShapedRun) + if (currentRun is ShapedTextRun currentShapedRun) { var firstCluster = currentShapedRun.GlyphRun.Metrics.FirstCluster; @@ -592,7 +592,7 @@ namespace Avalonia.Media.TextFormatting var rightToLeftIndex = index; var rightToLeftWidth = currentShapedRun.Size.Width; - while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextCharacters nextShapedRun) + while (rightToLeftIndex + 1 <= _textRuns.Count - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextRun nextShapedRun) { if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight) { @@ -624,12 +624,12 @@ namespace Avalonia.Media.TextFormatting for (int i = rightToLeftIndex - 1; i >= index; i--) { - if (TextRuns[i] is not ShapedTextCharacters) + if (TextRuns[i] is not ShapedTextRun) { continue; } - currentShapedRun = (ShapedTextCharacters)TextRuns[i]; + currentShapedRun = (ShapedTextRun)TextRuns[i]; currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength); @@ -769,7 +769,7 @@ namespace Avalonia.Media.TextFormatting var characterLength = 0; var endX = startX; - if (currentRun is ShapedTextCharacters currentShapedRun) + if (currentRun is ShapedTextRun currentShapedRun) { var offset = Math.Max(0, firstTextSourceIndex - currentPosition); @@ -883,7 +883,7 @@ namespace Avalonia.Media.TextFormatting return result; } - private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextCharacters currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength) + private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextRun currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength) { var startX = endX; @@ -945,7 +945,7 @@ namespace Avalonia.Media.TextFormatting private static sbyte GetRunBidiLevel(DrawableTextRun run, FlowDirection flowDirection) { - if (run is ShapedTextCharacters shapedTextCharacters) + if (run is ShapedTextRun shapedTextCharacters) { return shapedTextCharacters.BidiLevel; } @@ -1027,7 +1027,7 @@ namespace Avalonia.Media.TextFormatting { if (current.Level >= minLevelToReverse && current.Level % 2 != 0) { - if (current.Run is ShapedTextCharacters { IsReversed: false } shapedTextCharacters) + if (current.Run is ShapedTextRun { IsReversed: false } shapedTextCharacters) { shapedTextCharacters.Reverse(); } @@ -1145,7 +1145,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); @@ -1230,7 +1230,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); @@ -1294,7 +1294,7 @@ namespace Avalonia.Media.TextFormatting switch (currentRun) { - case ShapedTextCharacters shapedRun: + case ShapedTextRun shapedRun: { var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster; @@ -1303,7 +1303,7 @@ namespace Avalonia.Media.TextFormatting break; } - if (previousRun is ShapedTextCharacters previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight) + if (previousRun is ShapedTextRun previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight) { if (shapedRun.ShapedBuffer.IsLeftToRight) { @@ -1394,7 +1394,7 @@ namespace Avalonia.Media.TextFormatting { switch (_textRuns[index]) { - case ShapedTextCharacters textRun: + case ShapedTextRun textRun: { var textMetrics = new TextMetrics(textRun.Properties.Typeface.GlyphTypeface, textRun.Properties.FontRenderingEmSize); diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapeableTextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/UnshapedTextRun.cs similarity index 61% rename from src/Avalonia.Base/Media/TextFormatting/ShapeableTextCharacters.cs rename to src/Avalonia.Base/Media/TextFormatting/UnshapedTextRun.cs index 0e8d6e3e4a..817086db88 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapeableTextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/UnshapedTextRun.cs @@ -5,9 +5,9 @@ namespace Avalonia.Media.TextFormatting /// /// A group of characters that can be shaped. /// - public sealed class ShapeableTextCharacters : TextRun + public sealed class UnshapedTextRun : TextRun { - public ShapeableTextCharacters(CharacterBufferReference characterBufferReference, int length, + public UnshapedTextRun(CharacterBufferReference characterBufferReference, int length, TextRunProperties properties, sbyte biDiLevel) { CharacterBufferReference = characterBufferReference; @@ -24,30 +24,30 @@ namespace Avalonia.Media.TextFormatting public sbyte BidiLevel { get; } - public bool CanShapeTogether(ShapeableTextCharacters shapeableTextCharacters) + public bool CanShapeTogether(UnshapedTextRun unshapedTextRun) { - if (!CharacterBufferReference.Equals(shapeableTextCharacters.CharacterBufferReference)) + if (!CharacterBufferReference.Equals(unshapedTextRun.CharacterBufferReference)) { return false; } - if (BidiLevel != shapeableTextCharacters.BidiLevel) + if (BidiLevel != unshapedTextRun.BidiLevel) { return false; } if (!MathUtilities.AreClose(Properties.FontRenderingEmSize, - shapeableTextCharacters.Properties.FontRenderingEmSize)) + unshapedTextRun.Properties.FontRenderingEmSize)) { return false; } - if (Properties.Typeface != shapeableTextCharacters.Properties.Typeface) + if (Properties.Typeface != unshapedTextRun.Properties.Typeface) { return false; } - if (Properties.BaselineAlignment != shapeableTextCharacters.Properties.BaselineAlignment) + if (Properties.BaselineAlignment != unshapedTextRun.Properties.BaselineAlignment) { return false; } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 33d4fba5f1..7fc27b01f4 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -520,7 +520,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var expectedTextLine = formatter.FormatLine(new SingleBufferTextSource(text, defaultProperties), 0, double.PositiveInfinity, paragraphProperties); - var expectedRuns = expectedTextLine.TextRuns.Cast().ToList(); + var expectedRuns = expectedTextLine.TextRuns.Cast().ToList(); var expectedGlyphs = expectedRuns.SelectMany(x => x.GlyphRun.GlyphIndices).ToList(); @@ -539,7 +539,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = formatter.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties); - var shapedRuns = textLine.TextRuns.Cast().ToList(); + var shapedRuns = textLine.TextRuns.Cast().ToList(); var actualGlyphs = shapedRuns.SelectMany(x => x.GlyphRun.GlyphIndices).ToList(); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index a407b38eb1..2790bd6096 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -141,7 +141,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting black, textWrapping: TextWrapping.Wrap); - var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() + var expectedGlyphs = expected.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() .SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList(); var outer = new GraphemeEnumerator(new CharacterBufferRange(text)); @@ -174,7 +174,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting textWrapping: TextWrapping.Wrap, textStyleOverrides: spans); - var actualGlyphs = actual.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() + var actualGlyphs = actual.TextLines.Select(x => string.Join('|', x.TextRuns.Cast() .SelectMany(x => x.ShapedBuffer.GlyphIndices))).ToList(); Assert.Equal(expectedGlyphs.Count, actualGlyphs.Count); @@ -447,7 +447,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting 12.0f, Brushes.Black.ToImmutable()); - var shapedRun = (ShapedTextCharacters)layout.TextLines[0].TextRuns[0]; + var shapedRun = (ShapedTextRun)layout.TextLines[0].TextRuns[0]; var glyphRun = shapedRun.GlyphRun; @@ -481,7 +481,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var textRun in textLine.TextRuns) { - var shapedRun = (ShapedTextCharacters)textRun; + var shapedRun = (ShapedTextRun)textRun; var glyphClusters = shapedRun.ShapedBuffer.GlyphClusters; @@ -514,13 +514,13 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(1, layout.TextLines[0].TextRuns.Count); - Assert.Equal(expectedLength, ((ShapedTextCharacters)layout.TextLines[0].TextRuns[0]).GlyphRun.GlyphClusters.Count); + Assert.Equal(expectedLength, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).GlyphRun.GlyphClusters.Count); - Assert.Equal(5, ((ShapedTextCharacters)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[5]); + Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[5]); if (expectedLength == 7) { - Assert.Equal(5, ((ShapedTextCharacters)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[6]); + Assert.Equal(5, ((ShapedTextRun)layout.TextLines[0].TextRuns[0]).ShapedBuffer.GlyphClusters[6]); } } } @@ -555,7 +555,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; - var textRun = (ShapedTextCharacters)textLine.TextRuns[0]; + var textRun = (ShapedTextRun)textLine.TextRuns[0]; Assert.Equal(7, textRun.Length); @@ -775,7 +775,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(textLine.WidthIncludingTrailingWhitespace, rect.Width); } - var rects = layout.TextLines.SelectMany(x => x.TextRuns.Cast()) + var rects = layout.TextLines.SelectMany(x => x.TextRuns.Cast()) .SelectMany(x => x.ShapedBuffer.GlyphAdvances).ToArray(); for (var i = 0; i < SingleLineText.Length; i++) @@ -814,7 +814,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { Assert.True(textLine.Width <= maxWidth); - var actual = new string(textLine.TextRuns.Cast() + var actual = new string(textLine.TextRuns.Cast() .OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar) .SelectMany(x => new CharacterBufferRange(x.CharacterBufferReference, x.Length)).ToArray()); @@ -855,7 +855,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Brushes.Black, flowDirection: FlowDirection.RightToLeft); - var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextCharacters; + var firstRun = layout.TextLines[0].TextRuns[0] as ShapedTextRun; var hit = layout.HitTestPoint(new Point()); @@ -881,7 +881,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting currentX += advance; } - var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextCharacters; + var secondRun = layout.TextLines[0].TextRuns[1] as ShapedTextRun; hit = layout.HitTestPoint(new Point(firstRun.Size.Width, 0)); @@ -928,7 +928,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; - var firstRun = (ShapedTextCharacters)textLine.TextRuns[0]; + var firstRun = (ShapedTextRun)textLine.TextRuns[0]; var firstCluster = firstRun.ShapedBuffer.GlyphClusters[0]; @@ -987,7 +987,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = layout.TextLines[0]; - var shapedRuns = textLine.TextRuns.Cast().ToList(); + var shapedRuns = textLine.TextRuns.Cast().ToList(); var clusters = shapedRuns.SelectMany(x => x.ShapedBuffer.GlyphClusters).ToList(); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index d6257a0de8..ac2467407b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -92,7 +92,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var textRun in textLine.TextRuns.OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar)) { - var shapedRun = (ShapedTextCharacters)textRun; + var shapedRun = (ShapedTextRun)textRun; clusters.AddRange(shapedRun.IsReversed ? shapedRun.ShapedBuffer.GlyphClusters.Reverse() : @@ -139,7 +139,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var textRun in textLine.TextRuns.OrderBy(x => x.CharacterBufferReference.OffsetToFirstChar)) { - var shapedRun = (ShapedTextCharacters)textRun; + var shapedRun = (ShapedTextRun)textRun; clusters.AddRange(shapedRun.IsReversed ? shapedRun.ShapedBuffer.GlyphClusters.Reverse() : @@ -246,7 +246,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting formatter.FormatLine(textSource, 0, double.PositiveInfinity, new GenericTextParagraphProperties(defaultProperties)); - var clusters = textLine.TextRuns.Cast().SelectMany(x => x.ShapedBuffer.GlyphClusters) + var clusters = textLine.TextRuns.Cast().SelectMany(x => x.ShapedBuffer.GlyphClusters) .ToArray(); var previousCharacterHit = new CharacterHit(text.Length); @@ -308,7 +308,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting foreach (var run in textLine.TextRuns) { - var textRun = (ShapedTextCharacters)run; + var textRun = (ShapedTextRun)run; var glyphRun = textRun.GlyphRun; @@ -634,7 +634,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting formatter.FormatLine(textSource, 0, double.PositiveInfinity, new GenericTextParagraphProperties(defaultProperties)); - var textRuns = textLine.TextRuns.Cast().ToList(); + var textRuns = textLine.TextRuns.Cast().ToList(); var lineWidth = textLine.WidthIncludingTrailingWhitespace; @@ -732,14 +732,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting private static bool IsRightToLeft(TextLine textLine) { - return textLine.TextRuns.Cast().Any(x => !x.ShapedBuffer.IsLeftToRight); + return textLine.TextRuns.Cast().Any(x => !x.ShapedBuffer.IsLeftToRight); } private static List BuildGlyphClusters(TextLine textLine) { var glyphClusters = new List(); - var shapedTextRuns = textLine.TextRuns.Cast().ToList(); + var shapedTextRuns = textLine.TextRuns.Cast().ToList(); var lastCluster = -1; @@ -774,7 +774,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var lastCluster = -1; - var shapedTextRuns = textLine.TextRuns.Cast().ToList(); + var shapedTextRuns = textLine.TextRuns.Cast().ToList(); foreach (var textRun in shapedTextRuns) { @@ -820,16 +820,16 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var text = "0123"; var shaperOption = new TextShaperOptions(Typeface.Default.GlyphTypeface, 10, 0, CultureInfo.CurrentCulture); - var firstRun = new ShapedTextCharacters(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties); + var firstRun = new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties); var textRuns = new List { new CustomDrawableRun(), firstRun, new CustomDrawableRun(), - new ShapedTextCharacters(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties), + new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties), new CustomDrawableRun(), - new ShapedTextCharacters(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties) + new ShapedTextRun(TextShaper.Current.ShapeText(text, shaperOption), defaultProperties) }; var textSource = new FixedRunsTextSource(textRuns); @@ -885,14 +885,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textBounds = textLine.GetTextBounds(0, 3); - var firstRun = textLine.TextRuns[0] as ShapedTextCharacters; + var firstRun = textLine.TextRuns[0] as ShapedTextRun; Assert.Equal(1, textBounds.Count); Assert.Equal(firstRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(3, 4); - var secondRun = textLine.TextRuns[1] as ShapedTextCharacters; + var secondRun = textLine.TextRuns[1] as ShapedTextRun; Assert.Equal(1, textBounds.Count); Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); @@ -932,14 +932,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textBounds = textLine.GetTextBounds(0, 4); - var secondRun = textLine.TextRuns[1] as ShapedTextCharacters; + var secondRun = textLine.TextRuns[1] as ShapedTextRun; Assert.Equal(1, textBounds.Count); Assert.Equal(secondRun.Size.Width, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(4, 3); - var firstRun = textLine.TextRuns[0] as ShapedTextCharacters; + var firstRun = textLine.TextRuns[0] as ShapedTextRun; Assert.Equal(1, textBounds.Count); From dea807609fca0f355744291e22bba56acab6df9a Mon Sep 17 00:00:00 2001 From: daniel Date: Thu, 5 Jan 2023 18:13:53 +0200 Subject: [PATCH 154/213] Move UpdateLogicalTree Call to CurrentContent Change --- src/Avalonia.Controls/TransitioningContentControl.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 545032befb..2e0a36ad19 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -69,6 +69,10 @@ public class TransitioningContentControl : ContentControl { Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content)); } + else if (change.Property == CurrentContentProperty) + { + UpdateLogicalTree(change.OldValue, change.NewValue); + } } protected override void ContentChanged(AvaloniaPropertyChangedEventArgs e) @@ -94,8 +98,6 @@ public class TransitioningContentControl : ContentControl if (PageTransition != null) await PageTransition.Start(this, null, true, localToken); - UpdateLogicalTree(CurrentContent, content); - if (localToken.IsCancellationRequested) { return; From 490c9b1ffe6a69037dee03b723bc1ba80b0ba0b2 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Fri, 6 Jan 2023 00:58:20 +0300 Subject: [PATCH 155/213] [Text] fix tab width. It's too long --- samples/ControlCatalog/Pages/TextBoxPage.xaml | 2 +- .../Media/TextFormatting/TextParagraphProperties.cs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml index 6a4d9b7d0e..7408399873 100644 --- a/samples/ControlCatalog/Pages/TextBoxPage.xaml +++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml @@ -38,7 +38,7 @@ UseFloatingWatermark="True" PasswordChar="*" Text="Password" /> - + /// Gets the default incremental tab width. /// - public virtual double DefaultIncrementalTab - { - get { return 4 * DefaultTextRunProperties.FontRenderingEmSize; } - } + public virtual double DefaultIncrementalTab => 0; /// /// Gets the letter spacing. From 209861d9d405999c0e485a85637a61cfb2c1cb64 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 10:31:08 +1100 Subject: [PATCH 156/213] . --- ...rTransform_Applies_To_Opacity_Mask.expected.png} | Bin ...rTransform_Applies_To_Opacity_Mask.expected.png} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/TestFiles/Direct2D1/OpacityMask/{RenderTansform_Applies_To_Opacity_Mask.expected.png => RenderTransform_Applies_To_Opacity_Mask.expected.png} (100%) rename tests/TestFiles/Skia/OpacityMask/{RenderTansform_Applies_To_Opacity_Mask.expected.png => RenderTransform_Applies_To_Opacity_Mask.expected.png} (100%) diff --git a/tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png b/tests/TestFiles/Direct2D1/OpacityMask/RenderTransform_Applies_To_Opacity_Mask.expected.png similarity index 100% rename from tests/TestFiles/Direct2D1/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png rename to tests/TestFiles/Direct2D1/OpacityMask/RenderTransform_Applies_To_Opacity_Mask.expected.png diff --git a/tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png b/tests/TestFiles/Skia/OpacityMask/RenderTransform_Applies_To_Opacity_Mask.expected.png similarity index 100% rename from tests/TestFiles/Skia/OpacityMask/RenderTansform_Applies_To_Opacity_Mask.expected.png rename to tests/TestFiles/Skia/OpacityMask/RenderTransform_Applies_To_Opacity_Mask.expected.png From fb37ab1e7725deef8b54a69d21a17f92ba361c8d Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 10:46:29 +1100 Subject: [PATCH 157/213] leverage GetRequiredService --- samples/ControlCatalog.Desktop/Program.cs | 2 +- samples/ControlCatalog/Pages/TabControlPage.xaml.cs | 2 +- .../ViewModels/CursorPageViewModel.cs | 2 +- .../TransitioningContentControlPageViewModel.cs | 2 +- src/Avalonia.Base/Input/Cursor.cs | 3 +-- src/Avalonia.Base/Media/FontManager.cs | 5 +---- .../Media/TextFormatting/TextShaper.cs | 5 +---- src/Avalonia.Base/Rendering/RenderLoop.cs | 3 +-- src/Avalonia.Base/Threading/DispatcherTimer.cs | 8 +------- src/Avalonia.Controls/Platform/PlatformManager.cs | 13 +++---------- src/Avalonia.DesignerSupport/DesignWindowLoader.cs | 5 +---- .../Internal/ManagedFileChooserViewModel.cs | 2 +- src/Avalonia.OpenGL/Egl/EglInterface.cs | 2 +- src/Avalonia.X11/X11Framebuffer.cs | 2 +- src/Avalonia.X11/X11Platform.cs | 2 +- .../Converters/BitmapTypeConverter.cs | 2 +- .../Converters/IconTypeConverter.cs | 2 +- src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs | 2 +- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 6 +++--- .../Media/DWriteResourceFontLoader.cs | 2 +- .../Avalonia.Direct2D1/Media/DrawingContextImpl.cs | 2 +- .../Wpf/Direct2DImageSurface.cs | 2 +- .../Media/Fonts/FontFamilyLoaderTests.cs | 2 +- .../Avalonia.Controls.UnitTests/ContextMenuTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/FlyoutTests.cs | 2 +- .../MaskedTextBoxTests.cs | 2 +- .../Primitives/PopupTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 4 ++-- tests/Avalonia.Controls.UnitTests/TreeViewTests.cs | 9 ++++----- .../AutoSuspendHelperTest.cs | 6 +++--- tests/Avalonia.RenderTests/TestBase.cs | 2 +- tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs | 7 +------ 32 files changed, 42 insertions(+), 72 deletions(-) diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index 7b8b27fff7..4d28f15e2c 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -23,7 +23,7 @@ namespace ControlCatalog private static void ConfigureAssetAssembly(AppBuilder builder) { AvaloniaLocator.CurrentMutable - .GetService() + .GetRequiredService() .SetDefaultAssembly(typeof(App).Assembly); } } diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs index 413b6e1c75..f3f4bf6e93 100644 --- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs @@ -51,7 +51,7 @@ namespace ControlCatalog.Pages private static IBitmap LoadBitmap(string uri) { - var assets = AvaloniaLocator.Current!.GetService()!; + var assets = AvaloniaLocator.Current.GetRequiredService(); return new Bitmap(assets.Open(new Uri(uri))); } } diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs index 8a3f0ba947..12ca6af4f5 100644 --- a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs @@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels .Select(x => new StandardCursorModel(x)) .ToList(); - var loader = AvaloniaLocator.Current!.GetService()!; + var loader = AvaloniaLocator.Current.GetRequiredService()!; var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png")); var bitmap = new Bitmap(s); CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16)); diff --git a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs index 93857fd899..f4e0bc8912 100644 --- a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs @@ -19,7 +19,7 @@ namespace ControlCatalog.ViewModels { public TransitioningContentControlPageViewModel() { - var assetLoader = AvaloniaLocator.Current?.GetService()!; + var assetLoader = AvaloniaLocator.Current.GetRequiredService()!; var images = new string[] { diff --git a/src/Avalonia.Base/Input/Cursor.cs b/src/Avalonia.Base/Input/Cursor.cs index 8e79206f93..c555087879 100644 --- a/src/Avalonia.Base/Input/Cursor.cs +++ b/src/Avalonia.Base/Input/Cursor.cs @@ -71,8 +71,7 @@ namespace Avalonia.Input private static ICursorFactory GetCursorFactory() { - return AvaloniaLocator.Current.GetService() ?? - throw new Exception("Could not create Cursor: ICursorFactory not registered."); + return AvaloniaLocator.Current.GetRequiredService(); } } } diff --git a/src/Avalonia.Base/Media/FontManager.cs b/src/Avalonia.Base/Media/FontManager.cs index d92d003c2a..e82d5b7ba5 100644 --- a/src/Avalonia.Base/Media/FontManager.cs +++ b/src/Avalonia.Base/Media/FontManager.cs @@ -47,10 +47,7 @@ namespace Avalonia.Media return current; } - var fontManagerImpl = AvaloniaLocator.Current.GetService(); - - if (fontManagerImpl == null) - throw new InvalidOperationException("No font manager implementation was registered."); + var fontManagerImpl = AvaloniaLocator.Current.GetRequiredService(); current = new FontManager(fontManagerImpl); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs index 4aacec7c48..c161b08d20 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaper.cs @@ -29,10 +29,7 @@ namespace Avalonia.Media.TextFormatting return current; } - var textShaperImpl = AvaloniaLocator.Current.GetService(); - - if (textShaperImpl == null) - throw new InvalidOperationException("No text shaper implementation was registered."); + var textShaperImpl = AvaloniaLocator.Current.GetRequiredService(); current = new TextShaper(textShaperImpl); diff --git a/src/Avalonia.Base/Rendering/RenderLoop.cs b/src/Avalonia.Base/Rendering/RenderLoop.cs index 5a08bfc6a1..1f58ca3827 100644 --- a/src/Avalonia.Base/Rendering/RenderLoop.cs +++ b/src/Avalonia.Base/Rendering/RenderLoop.cs @@ -50,8 +50,7 @@ namespace Avalonia.Rendering { get { - return _timer ??= AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Cannot locate IRenderTimer."); + return _timer ??= AvaloniaLocator.Current.GetRequiredService(); } } diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 0c25d89722..8fc91a02d8 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -176,13 +176,7 @@ namespace Avalonia.Threading { if (!IsEnabled) { - var threading = AvaloniaLocator.Current.GetService(); - - if (threading == null) - { - throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered."); - } - + var threading = AvaloniaLocator.Current.GetRequiredService(); _timer = threading.StartTimer(_priority, Interval, InternalTick); } } diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index de7708e869..b01fc28831 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -21,26 +21,19 @@ namespace Avalonia.Controls.Platform } public static ITrayIconImpl? CreateTrayIcon() => - s_designerMode ? null : AvaloniaLocator.Current.GetService()?.CreateTrayIcon(); + s_designerMode ? null : AvaloniaLocator.Current.GetRequiredService().CreateTrayIcon(); public static IWindowImpl CreateWindow() { - var platform = AvaloniaLocator.Current.GetService(); - - if (platform == null) - { - throw new Exception("Could not CreateWindow(): IWindowingPlatform is not registered."); - } + var platform = AvaloniaLocator.Current.GetRequiredService(); return s_designerMode ? platform.CreateEmbeddableWindow() : platform.CreateWindow(); } public static IWindowImpl CreateEmbeddableWindow() { - var platform = AvaloniaLocator.Current.GetService(); - if (platform == null) - throw new Exception("Could not CreateEmbeddableWindow(): IWindowingPlatform is not registered."); + var platform = AvaloniaLocator.Current.GetRequiredService(); return platform.CreateEmbeddableWindow(); } } diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index b4cfffcdca..eff190c39e 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -18,12 +18,9 @@ namespace Avalonia.DesignerSupport Control control; using (PlatformManager.DesignerMode()) { - var loader = AvaloniaLocator.Current.GetService(); + var loader = AvaloniaLocator.Current.GetRequiredService(); var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); - if (loader == null) - throw new XamlLoadException("Runtime XAML loader is not registered"); - Uri baseUri = null; if (assemblyPath != null) { diff --git a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs index 7d0072c6ea..3de79927b2 100644 --- a/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs +++ b/src/Avalonia.Dialogs/Internal/ManagedFileChooserViewModel.cs @@ -117,7 +117,7 @@ namespace Avalonia.Dialogs.Internal ?? new ManagedFileChooserSources(); var sub1 = AvaloniaLocator.Current - .GetService() + .GetRequiredService() .Listen(ManagedFileChooserSources.MountedVolumes); var sub2 = Observable.FromEventPattern(ManagedFileChooserSources.MountedVolumes, diff --git a/src/Avalonia.OpenGL/Egl/EglInterface.cs b/src/Avalonia.OpenGL/Egl/EglInterface.cs index a913c05996..06aafb4f57 100644 --- a/src/Avalonia.OpenGL/Egl/EglInterface.cs +++ b/src/Avalonia.OpenGL/Egl/EglInterface.cs @@ -35,7 +35,7 @@ namespace Avalonia.OpenGL.Egl static Func Load(string library) { - var dyn = AvaloniaLocator.Current.GetService(); + var dyn = AvaloniaLocator.Current.GetRequiredService(); var lib = dyn.LoadLibrary(library); return (s) => dyn.GetProcAddress(lib, s, true); } diff --git a/src/Avalonia.X11/X11Framebuffer.cs b/src/Avalonia.X11/X11Framebuffer.cs index 94f930e9ec..a9fedff8b5 100644 --- a/src/Avalonia.X11/X11Framebuffer.cs +++ b/src/Avalonia.X11/X11Framebuffer.cs @@ -25,7 +25,7 @@ namespace Avalonia.X11 RowBytes = width * 4; Dpi = new Vector(96, 96) * factor; Format = PixelFormat.Bgra8888; - _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * height); + _blob = AvaloniaLocator.Current.GetRequiredService().AllocBlob(RowBytes * height); Address = _blob.Address; } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 9692e0a384..0f9ef72a3f 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -105,7 +105,7 @@ namespace Avalonia.X11 var gl = AvaloniaLocator.Current.GetService(); if (options.UseCompositor) - Compositor = new Compositor(AvaloniaLocator.Current.GetService()!, gl); + Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), gl); else RenderInterface = new(gl); diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs index dcd60f7a79..fc6a8557f3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/BitmapTypeConverter.cs @@ -24,7 +24,7 @@ namespace Avalonia.Markup.Xaml.Converters if(uri.IsAbsoluteUri && uri.IsFile) return new Bitmap(uri.LocalPath); - var assets = AvaloniaLocator.Current.GetService(); + var assets = AvaloniaLocator.Current.GetRequiredService(); return new Bitmap(assets.Open(uri, context.GetContextBaseUri())); } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs index 24b690b6f1..698f5a9327 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/IconTypeConverter.cs @@ -40,7 +40,7 @@ namespace Avalonia.Markup.Xaml.Converters if(uri.IsAbsoluteUri && uri.IsFile) return new WindowIcon(uri.LocalPath); - var assets = AvaloniaLocator.Current.GetService(); + var assets = AvaloniaLocator.Current.GetRequiredService(); return new WindowIcon(assets.Open(uri, context.GetContextBaseUri())); } } diff --git a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs index 5ca7f40d17..f7a86c11ff 100644 --- a/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs +++ b/src/Skia/Avalonia.Skia/SKTypefaceCollectionCache.cs @@ -37,7 +37,7 @@ namespace Avalonia.Skia var typeFaceCollection = new SKTypefaceCollection(); - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); foreach (var asset in fontAssets) { diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index dcb267b2a3..12d9471204 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -96,9 +96,9 @@ namespace Avalonia.Skia SKColorType colorType = format.ToSkColorType(); SKAlphaType alphaType = alphaFormat.ToSkAlphaType(); - - var runtimePlatform = AvaloniaLocator.Current?.GetService(); - + + var runtimePlatform = AvaloniaLocator.Current.GetRequiredService(); + if (runtimePlatform != null) { _bitmap = new SKBitmap(); diff --git a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs index e5f87e71a2..4663a6561f 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DWriteResourceFontLoader.cs @@ -22,7 +22,7 @@ namespace Avalonia.Direct2D1.Media { var factory1 = factory; - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); foreach (var asset in fontAssets) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 180ae491b3..3f2298eb22 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -409,7 +409,7 @@ namespace Avalonia.Direct2D1.Media } else { - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var dpi = new Vector(_deviceContext.DotsPerInch.Width, _deviceContext.DotsPerInch.Height); var pixelSize = PixelSize.FromSizeWithDpi(size, dpi); return (IDrawingContextLayerImpl)platform.CreateRenderTargetBitmap(pixelSize, dpi); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index a5474803a1..e607e255a5 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -127,7 +127,7 @@ namespace Avalonia.Win32.Interop.Wpf DeviceWindowHandle = GetDesktopWindow(), PresentationInterval = PresentInterval.Default }; - s_dxDevice = s_dxDevice ?? AvaloniaLocator.Current.GetService() + s_dxDevice = s_dxDevice ?? AvaloniaLocator.Current.GetRequiredService() .QueryInterface(); s_d3DDevice = new DeviceEx(s_d3DContext, 0, DeviceType.Hardware, IntPtr.Zero, CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.FpuPreserve, presentparams); diff --git a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs index aa042ffec8..afc25ab88e 100644 --- a/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/Fonts/FontFamilyLoaderTests.cs @@ -97,7 +97,7 @@ namespace Avalonia.Base.UnitTests.Media.Fonts { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) { - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); var fontFamily = new FontFamily("resm:Avalonia.Base.UnitTests.Assets?assembly=Avalonia.Base.UnitTests#Noto Mono"); diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index d6c521decd..baf933bd66 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -596,7 +596,7 @@ namespace Avalonia.Controls.UnitTests private static Window PreparedWindow(object content = null) { var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var windowImpl = Mock.Get(platform.CreateWindow()); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs index 496b5bc1b6..02767a21eb 100644 --- a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs +++ b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs @@ -570,7 +570,7 @@ namespace Avalonia.Controls.UnitTests private static Window PreparedWindow(object content = null) { var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var windowImpl = Mock.Get(platform.CreateWindow()); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index 90c6fd3b21..c1d9fad6f4 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -827,7 +827,7 @@ namespace Avalonia.Controls.UnitTests { AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - var clipboard = AvaloniaLocator.CurrentMutable.GetService(); + var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); clipboard.SetTextAsync(textInput).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index d4193d33ee..65957fda6d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -564,7 +564,7 @@ namespace Avalonia.Controls.UnitTests.Primitives using (CreateServices()) { var renderer = new Mock(); - var platform = AvaloniaLocator.Current.GetService(); + var platform = AvaloniaLocator.Current.GetRequiredService(); var windowImpl = Mock.Get(platform.CreateWindow()); windowImpl.Setup(x => x.CreateRenderer(It.IsAny())).Returns(renderer.Object); diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index bd6d5d55e2..531a2869cd 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -767,7 +767,7 @@ namespace Avalonia.Controls.UnitTests { AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - var clipboard = AvaloniaLocator.CurrentMutable.GetService(); + var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); clipboard.SetTextAsync(textInput).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); @@ -876,7 +876,7 @@ namespace Avalonia.Controls.UnitTests AvaloniaLocator.CurrentMutable.Bind().ToSingleton(); - var clipboard = AvaloniaLocator.CurrentMutable.GetService(); + var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService(); clipboard.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult(); RaiseKeyEvent(target, Key.V, KeyModifiers.Control); diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 81936711ef..f526465b9b 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -1248,7 +1248,7 @@ namespace Avalonia.Controls.UnitTests using (Application()) { var focus = FocusManager.Instance; - var navigation = AvaloniaLocator.Current.GetService(); + var navigation = AvaloniaLocator.Current.GetRequiredService(); var data = CreateTestTreeData(); var target = new TreeView @@ -1293,7 +1293,6 @@ namespace Avalonia.Controls.UnitTests using (Application()) { var focus = FocusManager.Instance; - var navigation = AvaloniaLocator.Current.GetService(); var data = CreateTestTreeData(); var selectedNode = new Node { Value = "Out of Tree Selected Item" }; @@ -1353,7 +1352,7 @@ namespace Avalonia.Controls.UnitTests var rootNode = tree[0]; - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = AvaloniaLocator.Current.GetRequiredService(); var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1400,7 +1399,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = AvaloniaLocator.Current.GetRequiredService(); var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs @@ -1447,7 +1446,7 @@ namespace Avalonia.Controls.UnitTests ClickContainer(fromContainer, KeyModifiers.None); ClickContainer(toContainer, KeyModifiers.Shift); - var keymap = AvaloniaLocator.Current.GetService(); + var keymap = AvaloniaLocator.Current.GetRequiredService(); var selectAllGesture = keymap.SelectAll.First(); var keyEvent = new KeyEventArgs diff --git a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs index 196375fb40..30326adba5 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/AutoSuspendHelperTest.cs @@ -47,7 +47,7 @@ namespace Avalonia.ReactiveUI.UnitTests using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) { var isLaunchingReceived = false; - var application = AvaloniaLocator.Current.GetService(); + var application = AvaloniaLocator.Current.GetRequiredService(); application.ApplicationLifetime = lifetime; // Initialize ReactiveUI Suspension as in real-world scenario. @@ -65,7 +65,7 @@ namespace Avalonia.ReactiveUI.UnitTests using (UnitTestApplication.Start(TestServices.MockWindowingPlatform)) using (var lifetime = new ExoticApplicationLifetimeWithoutLifecycleEvents()) { - var application = AvaloniaLocator.Current.GetService(); + var application = AvaloniaLocator.Current.GetRequiredService(); application.ApplicationLifetime = lifetime; Assert.Throws(() => new AutoSuspendHelper(application.ApplicationLifetime)); } @@ -88,7 +88,7 @@ namespace Avalonia.ReactiveUI.UnitTests using (var lifetime = new ClassicDesktopStyleApplicationLifetime()) { var shouldPersistReceived = false; - var application = AvaloniaLocator.Current.GetService(); + var application = AvaloniaLocator.Current.GetRequiredService(); application.ApplicationLifetime = lifetime; // Initialize ReactiveUI Suspension as in real-world scenario. diff --git a/tests/Avalonia.RenderTests/TestBase.cs b/tests/Avalonia.RenderTests/TestBase.cs index 313281d6c6..edde62f041 100644 --- a/tests/Avalonia.RenderTests/TestBase.cs +++ b/tests/Avalonia.RenderTests/TestBase.cs @@ -91,7 +91,7 @@ namespace Avalonia.Direct2D1.RenderTests var immediatePath = Path.Combine(OutputPath, testName + ".immediate.out.png"); var deferredPath = Path.Combine(OutputPath, testName + ".deferred.out.png"); var compositedPath = Path.Combine(OutputPath, testName + ".composited.out.png"); - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetRequiredService(); var pixelSize = new PixelSize((int)target.Width, (int)target.Height); var size = new Size(target.Width, target.Height); var dpiVector = new Vector(dpi, dpi); diff --git a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs index 2b1685f358..55ac16054d 100644 --- a/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs +++ b/tests/Avalonia.UnitTests/HarfBuzzFontManagerImpl.cs @@ -76,13 +76,8 @@ namespace Avalonia.UnitTests var asset = fontAssets.First(); - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); - if (assetLoader == null) - { - throw new NotSupportedException("IAssetLoader is not registered."); - } - var stream = assetLoader.Open(asset); return new HarfBuzzGlyphTypefaceImpl(stream); From 698977d2836e41bc6bb4083f14bf1ced746bb2fb Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 10:47:27 +1100 Subject: [PATCH 158/213] . --- samples/ControlCatalog/ViewModels/CursorPageViewModel.cs | 2 +- .../ViewModels/TransitioningContentControlPageViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs index 12ca6af4f5..f44d927801 100644 --- a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs @@ -18,7 +18,7 @@ namespace ControlCatalog.ViewModels .Select(x => new StandardCursorModel(x)) .ToList(); - var loader = AvaloniaLocator.Current.GetRequiredService()!; + var loader = AvaloniaLocator.Current.GetRequiredService(); var s = loader.Open(new Uri("avares://ControlCatalog/Assets/avalonia-32.png")); var bitmap = new Bitmap(s); CustomCursor = new Cursor(bitmap, new PixelPoint(16, 16)); diff --git a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs index f4e0bc8912..4505c11e95 100644 --- a/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TransitioningContentControlPageViewModel.cs @@ -19,7 +19,7 @@ namespace ControlCatalog.ViewModels { public TransitioningContentControlPageViewModel() { - var assetLoader = AvaloniaLocator.Current.GetRequiredService()!; + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); var images = new string[] { From 16ffe2f6f99d41adab83e58b9f0f927daaf92bae Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 10:50:23 +1100 Subject: [PATCH 159/213] use default literal --- .../Data/Converters/FuncMultiValueConverter.cs | 2 +- src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs | 2 +- src/Avalonia.Base/Media/DrawingContext.cs | 2 +- src/Avalonia.Base/Media/DrawingGroup.cs | 4 ++-- src/Avalonia.Base/Media/ImmediateDrawingContext.cs | 2 +- .../Composition/Expressions/ExpressionVariant.cs | 2 +- src/Avalonia.Base/Rendering/ImmediateRenderer.cs | 6 +++--- .../Rendering/Utilities/TileBrushCalculator.cs | 2 +- src/Avalonia.Base/Utilities/SingleOrDictionary.cs | 2 +- src/Avalonia.Base/Utilities/StringTokenizer.cs | 4 ++-- src/Avalonia.Controls.DataGrid/DataGrid.cs | 4 ++-- src/Avalonia.Controls.DataGrid/IndexToValueTable.cs | 2 +- src/Avalonia.Controls/Platform/InProcessDragSource.cs | 2 +- .../Presenters/ScrollContentPresenter.cs | 4 ++-- src/Avalonia.Controls/Utils/UndoRedoHelper.cs | 2 +- src/Avalonia.FreeDesktop/DBusMenu.cs | 8 ++++---- .../Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs | 2 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 4 ++-- src/Skia/Avalonia.Skia/GeometryImpl.cs | 2 +- .../Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs | 4 ++-- src/Windows/Avalonia.Win32/DataObject.cs | 2 +- src/Windows/Avalonia.Win32/OleDropTarget.cs | 2 +- .../ItemsPresenterTests_Virtualization_Simple.cs | 2 +- 23 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs b/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs index 2577cac743..5084d8e822 100644 --- a/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/FuncMultiValueConverter.cs @@ -38,7 +38,7 @@ namespace Avalonia.Data.Converters } else if (Equals(obj, default(TIn))) { - yield return default(TIn); + yield return default; } } } diff --git a/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs b/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs index 91fa459acb..fa298b53a3 100644 --- a/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs +++ b/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs @@ -7,7 +7,7 @@ namespace Avalonia.Layout { internal struct UvMeasure { - internal static readonly UvMeasure Zero = default(UvMeasure); + internal static readonly UvMeasure Zero = default; internal double U { get; set; } diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index eabd7c8274..077816c645 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -279,7 +279,7 @@ namespace Avalonia.Media OpacityMask, } - public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) + public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default) { if (context._states is null) throw new ObjectDisposedException(nameof(DrawingContext)); diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 4a46d89153..7d3b4c056e 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -76,8 +76,8 @@ namespace Avalonia.Media { using (context.PushPreTransform(Transform?.Value ?? Matrix.Identity)) using (context.PushOpacity(Opacity)) - using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default(DrawingContext.PushedState)) - using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default(DrawingContext.PushedState)) + using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default) + using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, GetBounds()) : default) { foreach (var drawing in Children) { diff --git a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs index 1e1a73437d..eb6f105680 100644 --- a/src/Avalonia.Base/Media/ImmediateDrawingContext.cs +++ b/src/Avalonia.Base/Media/ImmediateDrawingContext.cs @@ -218,7 +218,7 @@ namespace Avalonia.Media OpacityMask, } - internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) + internal PushedState(ImmediateDrawingContext context, PushedStateType type, Matrix matrix = default) { if (context._states is null) throw new ObjectDisposedException(nameof(ImmediateDrawingContext)); diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs index 6e53a138cd..21f14283b5 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionVariant.cs @@ -654,7 +654,7 @@ namespace Avalonia.Rendering.Composition.Expressions } } - res = default(T); + res = default; return false; } diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index 4909c78ed1..60b144e806 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -330,11 +330,11 @@ namespace Avalonia.Rendering ? visual is IVisualWithRoundRectClip roundClipVisual ? context.PushClip(new RoundedRect(bounds, roundClipVisual.ClipToBoundsRadius)) : context.PushClip(bounds) - : default(DrawingContext.PushedState)) + : default) #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 (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default) + using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default) using (context.PushTransformContainer()) { visual.Render(context); diff --git a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs index af2c7f71dc..6b34b44337 100644 --- a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs +++ b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs @@ -109,7 +109,7 @@ namespace Avalonia.Rendering.Utilities { if (IntermediateTransform != Matrix.Identity) return true; - if (SourceRect.Position != default(Point)) + if (SourceRect.Position != default) return true; if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) return false; diff --git a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs index 00cc5864f5..0eb7ae2e31 100644 --- a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs +++ b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs @@ -42,7 +42,7 @@ namespace Avalonia.Utilities { if (!_singleValue.HasValue || !EqualityComparer.Default.Equals(_singleValue.Value.Key, key)) { - value = default(TValue); + value = default; return false; } else diff --git a/src/Avalonia.Base/Utilities/StringTokenizer.cs b/src/Avalonia.Base/Utilities/StringTokenizer.cs index 726c9735ef..aad742e02b 100644 --- a/src/Avalonia.Base/Utilities/StringTokenizer.cs +++ b/src/Avalonia.Base/Utilities/StringTokenizer.cs @@ -63,7 +63,7 @@ namespace Avalonia.Utilities } else { - result = default(Int32); + result = default; return false; } } @@ -87,7 +87,7 @@ namespace Avalonia.Utilities } else { - result = default(double); + result = default; return false; } } diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 78de00078e..83a0dd3ce1 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -1118,7 +1118,7 @@ namespace Avalonia.Controls EnsureColumnHeadersVisibility(); if (!newValueCols) { - _columnHeadersPresenter.Measure(default(Size)); + _columnHeadersPresenter.Measure(default); } else { @@ -1159,7 +1159,7 @@ namespace Avalonia.Controls _topLeftCornerHeader.IsVisible = newValueRows && newValueCols; if (_topLeftCornerHeader.IsVisible) { - _topLeftCornerHeader.Measure(default(Size)); + _topLeftCornerHeader.Measure(default); } } diff --git a/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs b/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs index 65c3af344c..68a2fc562f 100644 --- a/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs +++ b/src/Avalonia.Controls.DataGrid/IndexToValueTable.cs @@ -422,7 +422,7 @@ namespace Avalonia.Controls else { found = false; - return default(T); + return default; } } diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 5b2356a7ce..196fd898cf 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -41,7 +41,7 @@ namespace Avalonia.Platform { _draggedData = data; _lastRoot = null; - _lastPosition = default(Point); + _lastPosition = default; _allowedEffects = allowedEffects; using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 328facba0b..51f85a3e3d 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -500,7 +500,7 @@ namespace Avalonia.Controls.Presenters if (e.OldValue != null) { - Offset = default(Vector); + Offset = default; } } @@ -542,7 +542,7 @@ namespace Avalonia.Controls.Presenters if (logicalScroll != scrollable.IsLogicalScrollEnabled) { UpdateScrollableSubscription(Child); - Offset = default(Vector); + Offset = default; InvalidateMeasure(); } else if (scrollable.IsLogicalScrollEnabled) diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 6ff72751a6..7b663dd017 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls.Utils public bool TryGetLastState(out TState? _state) { - _state = default(TState); + _state = default; if (!IsLastState) return false; diff --git a/src/Avalonia.FreeDesktop/DBusMenu.cs b/src/Avalonia.FreeDesktop/DBusMenu.cs index 3a1c65e7c9..7e22988270 100644 --- a/src/Avalonia.FreeDesktop/DBusMenu.cs +++ b/src/Avalonia.FreeDesktop/DBusMenu.cs @@ -36,10 +36,10 @@ namespace Avalonia.FreeDesktop.DBusMenu [Dictionary] class DBusMenuProperties { - public uint Version { get; set; } = default (uint); - public string? TextDirection { get; set; } = default (string); - public string? Status { get; set; } = default (string); - public string[]? IconThemePath { get; set; } = default (string[]); + public uint Version { get; set; } = default; + public string? TextDirection { get; set; } = default; + public string? Status { get; set; } = default; + public string[]? IconThemePath { get; set; } = default; } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index ac54365f51..6032f3a92c 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -25,7 +25,7 @@ namespace Avalonia.LinuxFramebuffer Surfaces = new object[] { _outputBackend }; - Invalidate(default(Rect)); + Invalidate(default); _inputBackend.Initialize(this, e => Input?.Invoke(e)); } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 48e8c761eb..8b71f4e17e 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -224,9 +224,9 @@ namespace Avalonia.Skia var impl = (GeometryImpl) geometry; var size = geometry.Bounds.Size; - using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper)) + using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default) using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, - size.Inflate(new Thickness(pen?.Thickness / 2 ?? 0))) : default(PaintWrapper)) + size.Inflate(new Thickness(pen?.Thickness / 2 ?? 0))) : default) { if (fill.Paint != null) { diff --git a/src/Skia/Avalonia.Skia/GeometryImpl.cs b/src/Skia/Avalonia.Skia/GeometryImpl.cs index 1141763097..15a3ebff40 100644 --- a/src/Skia/Avalonia.Skia/GeometryImpl.cs +++ b/src/Skia/Avalonia.Skia/GeometryImpl.cs @@ -245,7 +245,7 @@ namespace Avalonia.Skia { CachedStrokePath?.Dispose(); CachedGeometryRenderBounds = default; - _cachedStrokeWidth = default(float); + _cachedStrokeWidth = default; } } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs index a5474803a1..5526ee69ac 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs @@ -156,7 +156,7 @@ namespace Avalonia.Win32.Interop.Wpf _impl.ImageSource = _image; RemoveAndDispose(ref _backBuffer); - if (size == default(IntSize)) + if (size == default) { _image.Lock(); _image.SetBackBuffer(D3DResourceType.IDirect3DSurface9, IntPtr.Zero); @@ -171,7 +171,7 @@ namespace Avalonia.Win32.Interop.Wpf static void RemoveAndDispose(ref T d) where T : IDisposable { d?.Dispose(); - d = default(T); + d = default; } void Swap() diff --git a/src/Windows/Avalonia.Win32/DataObject.cs b/src/Windows/Avalonia.Win32/DataObject.cs index b7a567ea97..7d22e57a30 100644 --- a/src/Windows/Avalonia.Win32/DataObject.cs +++ b/src/Windows/Avalonia.Win32/DataObject.cs @@ -205,7 +205,7 @@ namespace Avalonia.Win32 if (string.IsNullOrEmpty(fmt) || !_wrapped.Contains(fmt)) return DV_E_FORMATETC; - * medium = default(Interop.STGMEDIUM); + * medium = default; medium->tymed = TYMED.TYMED_HGLOBAL; return WriteDataToHGlobal(fmt, ref medium->unionmember); } diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index b4eb6d8851..8bbdfe5fd9 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -121,7 +121,7 @@ namespace Avalonia.Win32 _dragDevice, RawDragEventType.DragLeave, _target, - default(Point), + default, null, DragDropEffects.None, RawInputModifiers.None diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 2d1dad6be0..469bcecfb9 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -569,7 +569,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var target = CreateTarget(itemCount: 10); var items = (IList)target.Items; target.ApplyTemplate(); - target.Measure(default(Size)); + target.Measure(default); target.Arrange(default); // Check for issue #591: this should not throw. From 208b0264c64e25282870c036e02c7794daf173ee Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 10:54:22 +1100 Subject: [PATCH 160/213] broken GetRequiredService for _useGL --- src/Browser/Avalonia.Browser/AvaloniaView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs index 153a8c6c1d..294216ee03 100644 --- a/src/Browser/Avalonia.Browser/AvaloniaView.cs +++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs @@ -106,7 +106,7 @@ namespace Avalonia.Browser _dpi = DomHelper.ObserveDpi(OnDpiChanged); - _useGL = AvaloniaLocator.Current.GetRequiredService() != null; + _useGL = AvaloniaLocator.Current.GetService() != null; if (_useGL) { From 9ea174ce0af7f1f9f89c0bc5492d74cb177b1f9d Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 10:58:47 +1100 Subject: [PATCH 161/213] Storage/getItem can be null https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem --- src/Browser/Avalonia.Browser/Interop/StorageHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs index c44af810d1..e50f8790ef 100644 --- a/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs +++ b/src/Browser/Avalonia.Browser/Interop/StorageHelper.cs @@ -45,7 +45,7 @@ internal static partial class StorageHelper [JSImport("StorageItem.getItems", AvaloniaModule.StorageModuleName)] [return: JSMarshalAs>] - public static partial Task GetItems(JSObject item); + public static partial Task GetItems(JSObject item); [JSImport("StorageItems.itemsArray", AvaloniaModule.StorageModuleName)] public static partial JSObject[] ItemsArray(JSObject item); From d53ce9a0e9e2004d447df81311d862e90c96ec98 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:04:20 +1100 Subject: [PATCH 162/213] Update BrowserStorageProvider.cs --- .../Storage/BrowserStorageProvider.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs index dc580f0fcb..0bebcb9f63 100644 --- a/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs +++ b/src/Browser/Avalonia.Browser/Storage/BrowserStorageProvider.cs @@ -29,11 +29,11 @@ internal class BrowserStorageProvider : IStorageProvider await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - var (types, exludeAll) = ConvertFileTypes(options.FileTypeFilter); + var (types, excludeAll) = ConvertFileTypes(options.FileTypeFilter); try { - using var items = await StorageHelper.OpenFileDialog(startIn, options.AllowMultiple, types, exludeAll); + using var items = await StorageHelper.OpenFileDialog(startIn, options.AllowMultiple, types, excludeAll); if (items is null) { return Array.Empty(); @@ -63,11 +63,11 @@ internal class BrowserStorageProvider : IStorageProvider await _lazyModule.Value; var startIn = (options.SuggestedStartLocation as JSStorageItem)?.FileHandle; - var (types, exludeAll) = ConvertFileTypes(options.FileTypeChoices); + var (types, excludeAll) = ConvertFileTypes(options.FileTypeChoices); try { - var item = await StorageHelper.SaveFileDialog(startIn, options.SuggestedFileName, types, exludeAll); + var item = await StorageHelper.SaveFileDialog(startIn, options.SuggestedFileName, types, excludeAll); return item is not null ? new JSStorageFile(item) : null; } catch (JSException ex) when (ex.Message.Contains(PickerCancelMessage, StringComparison.Ordinal)) @@ -127,9 +127,9 @@ internal class BrowserStorageProvider : IStorageProvider types = null; } - var inlcudeAll = input?.Contains(FilePickerFileTypes.All) == true || types is null; + var includeAll = input?.Contains(FilePickerFileTypes.All) == true || types is null; - return (types, !inlcudeAll); + return (types, !includeAll); } } From 54c96bc89469708dfbf0687d6dc6e2f9bd382464 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:05:31 +1100 Subject: [PATCH 163/213] Update IOSStorageProvider.cs --- src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs index 834418437b..5fdb7ecce1 100644 --- a/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs +++ b/src/iOS/Avalonia.iOS/Storage/IOSStorageProvider.cs @@ -34,7 +34,7 @@ internal class IOSStorageProvider : IStorageProvider UIDocumentPickerViewController documentPicker; if (OperatingSystem.IsIOSVersionAtLeast(14)) { - var allowedUtis = options.FileTypeFilter?.SelectMany(f => + var allowedUtils = options.FileTypeFilter?.SelectMany(f => { // We check for OS version outside of the lambda, it's safe. #pragma warning disable CA1416 @@ -56,18 +56,18 @@ internal class IOSStorageProvider : IStorageProvider UTTypes.Item, UTTypes.Data }; - documentPicker = new UIDocumentPickerViewController(allowedUtis!, false); + documentPicker = new UIDocumentPickerViewController(allowedUtils!, false); } else { - var allowedUtis = options.FileTypeFilter?.SelectMany(f => f.AppleUniformTypeIdentifiers ?? Array.Empty()) + var allowedUtils = options.FileTypeFilter?.SelectMany(f => f.AppleUniformTypeIdentifiers ?? Array.Empty()) .ToArray() ?? new[] { UTTypeLegacy.Content, UTTypeLegacy.Item, "public.data" }; - documentPicker = new UIDocumentPickerViewController(allowedUtis, UIDocumentPickerMode.Open); + documentPicker = new UIDocumentPickerViewController(allowedUtils, UIDocumentPickerMode.Open); } using (documentPicker) From 4b1a5a28b16d19aafa3155d33914a2e4535a3206 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:06:47 +1100 Subject: [PATCH 164/213] OpenFilePickerAsync cant return null --- samples/ControlCatalog/ViewModels/ContextPageViewModel.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs index 4cfb00625e..4a5462a58b 100644 --- a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs @@ -56,12 +56,9 @@ namespace ControlCatalog.ViewModels var result = await window.StorageProvider.OpenFilePickerAsync(new Avalonia.Platform.Storage.FilePickerOpenOptions() { AllowMultiple = true }); - if (result != null) + foreach (var file in result) { - foreach (var file in result) - { - System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}"); - } + System.Diagnostics.Debug.WriteLine($"Opened: {file.Name}"); } } From 374c0c4e3bd596d7166e3634c1ae0e146f894210 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:08:04 +1100 Subject: [PATCH 165/213] screens in ScreenPage sampel cant be null --- samples/ControlCatalog/Pages/ScreenPage.cs | 58 +++++++++++----------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 6af4cf353e..b5b80fb147 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -36,44 +36,44 @@ namespace ControlCatalog.Pages var drawBrush = Brushes.Black; Pen p = new Pen(drawBrush); - if (screens != null) - foreach (Screen screen in screens) + + foreach (Screen screen in screens) + { + if (screen.Bounds.X / 10f < _leftMost) { - if (screen.Bounds.X / 10f < _leftMost) - { - _leftMost = screen.Bounds.X / 10f; - Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); - return; - } + _leftMost = screen.Bounds.X / 10f; + Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background); + return; + } - Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f, - screen.Bounds.Height / 10f); - Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f, - screen.WorkingArea.Height / 10f); - - context.DrawRectangle(p, boundsRect); - context.DrawRectangle(p, workingAreaRect); + Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f, + screen.Bounds.Height / 10f); + Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f, + screen.WorkingArea.Height / 10f); + context.DrawRectangle(p, boundsRect); + context.DrawRectangle(p, workingAreaRect); - var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height)); - formattedText = - CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); + var formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height)); - formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); + formattedText = + CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); - formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); + formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); + formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); - formattedText = - CreateFormattedText( - $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"); - context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80)); - } + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); + + formattedText = + CreateFormattedText( + $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}"); + context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 80)); + } context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f, w.Bounds.Width / 10, w.Bounds.Height / 10)); } From 887153b3e829e41f1cd704c3200782ee88ce1076 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 5 Jan 2023 19:09:06 -0500 Subject: [PATCH 166/213] Restore RaisePropertyChanged --- src/Avalonia.Base/AvaloniaObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index a3a732428e..dc94dfba40 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -621,7 +621,7 @@ namespace Avalonia /// The old property value. /// The new property value. /// The priority of the binding that produced the value. - private protected void RaisePropertyChanged( + protected void RaisePropertyChanged( DirectPropertyBase property, Optional oldValue, BindingValue newValue, From 1b71714b76938278b744d475eb64b08d9156c187 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:09:22 +1100 Subject: [PATCH 167/213] brush in cataloge sample can be null --- samples/ControlCatalog/Pages/PointerCanvas.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/PointerCanvas.cs b/samples/ControlCatalog/Pages/PointerCanvas.cs index 7d26da8603..da1ff5442d 100644 --- a/samples/ControlCatalog/Pages/PointerCanvas.cs +++ b/samples/ControlCatalog/Pages/PointerCanvas.cs @@ -24,7 +24,7 @@ public class PointerCanvas : Control { struct CanvasPoint { - public IBrush Brush; + public IBrush? Brush; public Point Point; public double Radius; public double? Pressure; From c89f221f6d2f7bc9fef9ea9bca7de7fc41ba8105 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:14:29 +1100 Subject: [PATCH 168/213] remove some duplicate casts --- .../Controls/NameScopeExtensions.cs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Controls/NameScopeExtensions.cs b/src/Avalonia.Base/Controls/NameScopeExtensions.cs index 3895b6ceb9..7d80d2fde7 100644 --- a/src/Avalonia.Base/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Base/Controls/NameScopeExtensions.cs @@ -25,13 +25,18 @@ namespace Avalonia.Controls var result = nameScope.Find(name); - if (result != null && !(result is T)) + if (result == null) { - throw new InvalidOperationException( - $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); + return (T?)result; + } + + if (result is T typed) + { + return typed; } - return (T?)result; + throw new InvalidOperationException( + $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); } /// @@ -74,13 +79,13 @@ namespace Avalonia.Controls throw new KeyNotFoundException($"Could not find control '{name}'."); } - if (!(result is T)) + if (result is T typed) { - throw new InvalidOperationException( - $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); + return typed; } - return (T)result; + throw new InvalidOperationException( + $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); } /// From 4ab3eb106423fe0e2baaa43311e5bb1e561db98b Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:18:40 +1100 Subject: [PATCH 169/213] . --- src/Avalonia.Build.Tasks/ComInteropHelper.cs | 4 +--- .../ScrollViewerIRefreshInfoProviderAdapter.cs | 4 ++-- .../Templates/FuncTreeDataTemplate`1.cs | 4 ++-- .../ViewModels/ControlDetailsViewModel.cs | 8 ++++---- .../Markup/Parsers/SelectorGrammar.cs | 12 +++++++----- src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs | 2 +- .../Avalonia.Win32/WindowImpl.CustomCaptionProc.cs | 2 +- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Build.Tasks/ComInteropHelper.cs b/src/Avalonia.Build.Tasks/ComInteropHelper.cs index 007231417d..c990741e1e 100644 --- a/src/Avalonia.Build.Tasks/ComInteropHelper.cs +++ b/src/Avalonia.Build.Tasks/ComInteropHelper.cs @@ -65,10 +65,8 @@ namespace Avalonia.Build.Tasks { Instruction instruction = instructions[i]; - if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference) + if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference methodDescription) { - var methodDescription = (MethodReference)instruction.Operand; - if (methodDescription.Name.StartsWith("Calli") && methodDescription.DeclaringType.Name == "LocalInterop") { var callSite = new CallSite(methodDescription.ReturnType) { CallingConvention = MethodCallingConvention.StdCall }; diff --git a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs index c3aebc82c5..844973bd28 100644 --- a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs +++ b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs @@ -197,12 +197,12 @@ namespace Avalonia.Controls.PullToRefresh throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual"); } - if (content.Parent is not InputElement) + if (content.Parent is not InputElement parent) { throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement"); } - MakeInteractionSource(content.Parent as InputElement); + MakeInteractionSource(parent); if (_scrollViewer != null) { diff --git a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs index d4ecdd6cf0..a093d976f7 100644 --- a/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs +++ b/src/Avalonia.Controls/Templates/FuncTreeDataTemplate`1.cs @@ -59,7 +59,7 @@ namespace Avalonia.Controls.Templates /// The untyped function. private static Func CastMatch(Func f) { - return o => (o is T) && f((T)o); + return o => o is T arg && f(arg); } /// @@ -72,7 +72,7 @@ namespace Avalonia.Controls.Templates { return (o, s) => f((T)o!, s); } - + /// /// Casts a function with a typed parameter to an untyped function. /// diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 5cf9e17ecf..8bff9ccde0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -33,12 +33,12 @@ namespace Avalonia.Diagnostics.ViewModels { _avaloniaObject = avaloniaObject; - TreePage = treePage; - Layout = avaloniaObject is Visual - ? new ControlLayoutViewModel((Visual)avaloniaObject) + TreePage = treePage; + Layout = avaloniaObject is Visual visual + ? new ControlLayoutViewModel(visual) : default; - NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString()); + NavigateToProperty(_avaloniaObject, (_avaloniaObject as Control)?.Name ?? _avaloniaObject.ToString()); AppliedStyles = new ObservableCollection(); PseudoClasses = new ObservableCollection(); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 4d6d16a3ce..dbf37f0900 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -552,7 +552,8 @@ namespace Avalonia.Markup.Parsers public override bool Equals(object? obj) { - return obj is ClassSyntax && ((ClassSyntax)obj).Class == Class; + return obj is ClassSyntax syntax && + syntax.Class == Class; } } @@ -562,7 +563,8 @@ namespace Avalonia.Markup.Parsers public override bool Equals(object? obj) { - return obj is NameSyntax && ((NameSyntax)obj).Name == Name; + return obj is NameSyntax syntax && + syntax.Name == Name; } } @@ -574,9 +576,9 @@ namespace Avalonia.Markup.Parsers public override bool Equals(object? obj) { - return obj is PropertySyntax && - ((PropertySyntax)obj).Property == Property && - ((PropertySyntax)obj).Value == Value; + return obj is PropertySyntax syntax && + syntax.Property == Property && + syntax.Value == Value; } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs index b8648dfc50..3bfbf4bd92 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/IntSize.cs @@ -28,7 +28,7 @@ namespace Avalonia.Win32.Interop.Wpf public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; - return obj is IntSize && Equals((IntSize) obj); + return obj is IntSize size && Equals(size); } public override int GetHashCode() diff --git a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs index d393bd304a..fb27ab7856 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.CustomCaptionProc.cs @@ -109,7 +109,7 @@ namespace Avalonia.Win32 if (_owner is Window window) { - var visual = window.Renderer.HitTestFirst(position, (Visual)_owner, x => + var visual = window.Renderer.HitTestFirst(position, window, x => { if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) { From 0520d25ed580b86b3e3d1d7d4c4d614ce009d887 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:23:21 +1100 Subject: [PATCH 170/213] missing OnItemTemplatePropertyChanged call in AutoCompleteBox --- src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index e0d986f2b4..f9978d2795 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -474,6 +474,7 @@ namespace Avalonia.Controls FilterModeProperty.Changed.AddClassHandler((x,e) => x.OnFilterModePropertyChanged(e)); ItemFilterProperty.Changed.AddClassHandler((x,e) => x.OnItemFilterPropertyChanged(e)); ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); + ItemTemplateProperty.Changed.AddClassHandler((x,e) => x.OnItemTemplatePropertyChanged(e)); IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnControlIsEnabledChanged(e)); } From 81c06203676899fd45135e9390aeb002f155de61 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:24:35 +1100 Subject: [PATCH 171/213] Update src/Avalonia.Controls/Platform/PlatformManager.cs Co-authored-by: Max Katz --- src/Avalonia.Controls/Platform/PlatformManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Platform/PlatformManager.cs b/src/Avalonia.Controls/Platform/PlatformManager.cs index b01fc28831..79ace0b329 100644 --- a/src/Avalonia.Controls/Platform/PlatformManager.cs +++ b/src/Avalonia.Controls/Platform/PlatformManager.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.Platform } public static ITrayIconImpl? CreateTrayIcon() => - s_designerMode ? null : AvaloniaLocator.Current.GetRequiredService().CreateTrayIcon(); + s_designerMode ? null : AvaloniaLocator.Current.GetService()?.CreateTrayIcon(); public static IWindowImpl CreateWindow() From cc6f0d1af15cf4a37e16f0ea9469c4171d740f8f Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:28:59 +1100 Subject: [PATCH 172/213] should GetLayoutVisibleWindowDiscardAnchor be used? --- .../Repeater/ViewportManager.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index 10e3dd57a0..28d6911350 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -124,22 +124,6 @@ namespace Avalonia.Controls } } } - - private Rect GetLayoutVisibleWindowDiscardAnchor() - { - var visibleWindow = _visibleWindow; - - if (HasScroller) - { - visibleWindow = new Rect( - visibleWindow.X + _layoutExtent.X + _expectedViewportShift.X + _unshiftableShift.X, - visibleWindow.Y + _layoutExtent.Y + _expectedViewportShift.Y + _unshiftableShift.Y, - visibleWindow.Width, - visibleWindow.Height); - } - - return visibleWindow; - } public Rect GetLayoutVisibleWindow() { From ebb7a95c0e6046bea6fc6a13ede18991e94abc56 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 11:32:04 +1100 Subject: [PATCH 173/213] missing ScrollGestureEndedEvent mapping --- src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 328facba0b..61ad5e802b 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -94,6 +94,7 @@ namespace Avalonia.Controls.Presenters { AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested); AddHandler(Gestures.ScrollGestureEvent, OnScrollGesture); + AddHandler(Gestures.ScrollGestureEndedEvent, OnScrollGestureEnded); this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription); } From a642cba9d24a91dc1006d91fc7fb28f7a7870233 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 14:51:01 +1100 Subject: [PATCH 174/213] Update NameScopeExtensions.cs --- src/Avalonia.Base/Controls/NameScopeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Controls/NameScopeExtensions.cs b/src/Avalonia.Base/Controls/NameScopeExtensions.cs index 7d80d2fde7..0abd5e56f7 100644 --- a/src/Avalonia.Base/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Base/Controls/NameScopeExtensions.cs @@ -27,7 +27,7 @@ namespace Avalonia.Controls if (result == null) { - return (T?)result; + return null; } if (result is T typed) From 7db93a75e42535c8125437d71a199e6547a2cdbe Mon Sep 17 00:00:00 2001 From: daniel Date: Fri, 6 Jan 2023 08:54:04 +0200 Subject: [PATCH 175/213] Move missing calling InvalidateMirrorTransform --- src/Avalonia.Base/Visual.cs | 1 + src/Avalonia.Controls/Control.cs | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 5930df5483..7e22303964 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -480,6 +480,7 @@ namespace Avalonia { AttachToCompositor(compositingRenderer.Compositor); } + InvalidateMirrorTransform(); OnAttachedToVisualTree(e); AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 26db431a0e..ed24c3c7c2 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -392,14 +392,6 @@ namespace Avalonia.Controls OnUnloadedCore(); } - /// - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - - InvalidateMirrorTransform(); - } - /// protected override void OnGotFocus(GotFocusEventArgs e) { From 1396c5aac7ba0987e06d828589b1c53b52726a62 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 19:48:05 +1100 Subject: [PATCH 176/213] Update WriteableBitmapImpl.cs --- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 12d9471204..9864a14a9c 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -97,7 +97,7 @@ namespace Avalonia.Skia SKColorType colorType = format.ToSkColorType(); SKAlphaType alphaType = alphaFormat.ToSkAlphaType(); - var runtimePlatform = AvaloniaLocator.Current.GetRequiredService(); + var runtimePlatform = AvaloniaLocator.Current.GetService(); if (runtimePlatform != null) { From 5fdb513303b7b6cccca5b5e7416e34c3e061afa5 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Fri, 6 Jan 2023 12:18:15 +0300 Subject: [PATCH 177/213] [Text] [Debug] fix Text output of TextRunDebuggerProxy --- src/Avalonia.Base/Media/TextFormatting/TextRun.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs index 0306054767..e79c2ed8b3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs @@ -44,7 +44,7 @@ namespace Avalonia.Media.TextFormatting fixed (char* charsPtr = characterBuffer.Span) { - return new string(charsPtr, 0, characterBuffer.Span.Length); + return new string(charsPtr, _textRun.CharacterBufferReference.OffsetToFirstChar, _textRun.Length); } } } From ac01ee00e68e0bdfbe6f0992a4743bf4472561ae Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 6 Jan 2023 20:38:05 +1100 Subject: [PATCH 178/213] . --- samples/Directory.Build.props | 5 ++--- tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props index 471c42ec07..3b14f0ce12 100644 --- a/samples/Directory.Build.props +++ b/samples/Directory.Build.props @@ -2,9 +2,8 @@ false $(MSBuildThisFileDirectory)..\src\tools\Avalonia.Designer.HostApp\bin\Debug\netcoreapp2.0\Avalonia.Designer.HostApp.dll + false + 11 - - false - diff --git a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj index b9c2a619d9..17448ade76 100644 --- a/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj +++ b/tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj @@ -4,7 +4,6 @@ false Library false - latest ..\..\build\avalonia.snk false true From d1037a73699bfff23cd25a9c9d08bf66e3948e70 Mon Sep 17 00:00:00 2001 From: AlmightyJu Date: Fri, 6 Jan 2023 14:26:00 +0000 Subject: [PATCH 179/213] Fix DataGridTemplateColumn reusing edit control when exiting edit mode --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 1 + src/Avalonia.Controls.DataGrid/DataGridColumn.cs | 11 +++++++++++ .../DataGridTemplateColumn.cs | 16 ++++++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 78de00078e..cadecc03bc 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -4152,6 +4152,7 @@ namespace Avalonia.Controls if (exitEditingMode) { + CurrentColumn.EndCellEditInternal(); _editingColumnIndex = -1; editingCell.UpdatePseudoClasses(); diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index d9d80acdc3..63e7e1e2b3 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -800,11 +800,22 @@ namespace Avalonia.Controls protected internal virtual void RefreshCellContent(Control element, string propertyName) { } + /// + /// When overridden in a derived class, called when a cell in the column exits editing mode. + /// + protected virtual void EndCellEdit() + { } + internal void CancelCellEditInternal(Control editingElement, object uneditedValue) { CancelCellEdit(editingElement, uneditedValue); } + internal void EndCellEditInternal() + { + EndCellEdit(); + } + /// /// Coerces a DataGridLength to a valid value. If any value components are double.NaN, this method /// coerces them to a proper initial value. For star columns, the desired width is calculated based diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index 24ae358dcc..516e9cf6c2 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -55,17 +55,25 @@ namespace Avalonia.Controls get => _cellEditingCellTemplate; set => SetAndRaise(CellEditingTemplateProperty, ref _cellEditingCellTemplate, value); } - - private static void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) + + private bool _forceGenerateCellFromTemplate; + + protected override void EndCellEdit() { - var oldValue = (IDataTemplate)e.OldValue; - var value = (IDataTemplate)e.NewValue; + //the next call to generate element should not resuse the current content as we need to exit edit mode + _forceGenerateCellFromTemplate = true; + base.EndCellEdit(); } protected override Control GenerateElement(DataGridCell cell, object dataItem) { if (CellTemplate != null) { + if (_forceGenerateCellFromTemplate) + { + _forceGenerateCellFromTemplate = false; + return CellTemplate.Build(dataItem); + } return (CellTemplate is IRecyclingDataTemplate recyclingDataTemplate) ? recyclingDataTemplate.Build(dataItem, cell.Content as Control) : CellTemplate.Build(dataItem); From 84206e93dd9980e9b590398e50e1ba780cb5ce7c Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 7 Jan 2023 03:45:50 +0300 Subject: [PATCH 180/213] [Text] Optimize grapheme and add performance benchmark --- .../TextFormatting/FormattedTextSource.cs | 2 +- .../Media/TextFormatting/TextCharacters.cs | 4 +- .../Media/TextFormatting/Unicode/Grapheme.cs | 14 +++-- .../Unicode/GraphemeEnumerator.cs | 4 +- src/Avalonia.Controls/TextBox.cs | 2 +- .../GraphemeBreakClassTrieGeneratorTests.cs | 4 +- .../Text/HugeTextLayout.cs | 62 +++++++++++++++++++ .../Media/TextFormatting/TextLayoutTests.cs | 8 +-- 8 files changed, 83 insertions(+), 17 deletions(-) create mode 100644 tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index 4472ba87eb..49d94b511d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -132,7 +132,7 @@ namespace Avalonia.Media.TextFormatting { var grapheme = graphemeEnumerator.Current; - finalLength += grapheme.Text.Length; + finalLength += grapheme.Length; if (finalLength >= length) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index db035b8750..1a48151834 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -196,7 +196,7 @@ namespace Avalonia.Media.TextFormatting break; } - count += grapheme.Text.Length; + count += grapheme.Length; } return new UnshapedTextRun(characterBufferRange.CharacterBufferReference, count, defaultProperties, biDiLevel); @@ -264,7 +264,7 @@ namespace Avalonia.Media.TextFormatting } } - length += currentGrapheme.Text.Length; + length += currentGrapheme.Length; } return length > 0; diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs index 69015fb17d..f75168083c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Grapheme.cs @@ -7,10 +7,11 @@ namespace Avalonia.Media.TextFormatting.Unicode /// public readonly ref struct Grapheme { - public Grapheme(Codepoint firstCodepoint, ReadOnlySpan text) + public Grapheme(Codepoint firstCodepoint, int offset, int length) { FirstCodepoint = firstCodepoint; - Text = text; + Offset = offset; + Length = length; } /// @@ -19,8 +20,13 @@ namespace Avalonia.Media.TextFormatting.Unicode public Codepoint FirstCodepoint { get; } /// - /// The text that is representing the . + /// The Offset to the FirstCodepoint /// - public ReadOnlySpan Text { get; } + public int Offset { get; } + + /// + /// The length of the grapheme cluster + /// + public int Length { get; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs index 5ca120c856..dc21e06813 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs @@ -185,9 +185,7 @@ namespace Avalonia.Media.TextFormatting.Unicode Return: - var text = _text.Take(processor.CurrentCodeUnitOffset); - - Current = new Grapheme(firstCodepoint, text.Span); + Current = new Grapheme(firstCodepoint, _text.OffsetToFirstChar, processor.CurrentCodeUnitOffset); _text = _text.Skip(processor.CurrentCodeUnitOffset); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 9a2ee12dc3..0e1024c027 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -981,7 +981,7 @@ namespace Avalonia.Controls } } - length += grapheme.Text.Length; + length += grapheme.Length; } if (length < input.Length) diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs index 4e0207a85d..c57bd6c002 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs @@ -42,7 +42,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting enumerator.MoveNext(); - var actual = enumerator.Current.Text; + var actual = text.AsSpan(enumerator.Current.Offset, enumerator.Current.Length); var pass = true; @@ -93,7 +93,7 @@ namespace Avalonia.Base.UnitTests.Media.TextFormatting while (enumerator.MoveNext()) { - Assert.Equal(1, enumerator.Current.Text.Length); + Assert.Equal(1, enumerator.Current.Length); count++; } diff --git a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs new file mode 100644 index 0000000000..e1b3c6a99c --- /dev/null +++ b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs @@ -0,0 +1,62 @@ +using System; +using Avalonia.Media; +using Avalonia.Media.TextFormatting; +using Avalonia.UnitTests; +using BenchmarkDotNet.Attributes; + +namespace Avalonia.Benchmarks.Text; + +[MemoryDiagnoser] +public class HugeTextLayout : IDisposable +{ + private readonly IDisposable _app; + + public HugeTextLayout() + { + _app = UnitTestApplication.Start( + TestServices.StyledWindow.With( + renderInterface: new NullRenderingPlatform(), + threadingInterface: new NullThreadingPlatform(), + standardCursorFactory: new NullCursorFactory())); + } + + private const string Text = @"Though, the objectives of the development of the prominent landmarks can be neglected in most cases, it should be realized that after the completion of the strategic decision gives rise to The Expertise of Regular Program (Carlton Cartwright in The Book of the Key Factor) +A number of key issues arise from the belief that the explicit examination of strategic management should correlate with the conceptual design. +By all means, the unification of the reliably developed techniques indicates the importance of the ultimate advantage of episodic skill over alternate practices. +Let's consider, that the portion of the continuing support can be regarded as relentlessly insignificant. The hardware maintenance focuses our attention on the structure absorption. The real reason of the permanent growth drastically the preliminary action plan the ultimate advantage of useful probability over alternate practices. +Let it not be said that a section of the essential component discards the principle of the more interconnection of critical thinking with productivity boosting of the referential arguments. +One should, however, not forget that concentration of violations of the strategic management requires urgent actions to be taken towards the comprehensive set of policy statements. Therefore, the concept of the design aspects can be treated as the only solution. +In a loose sense concentration of the center of the critical thinking provides a deep insight into the emergency planning. The comparison is quite a powerful matter. +Resulting from review or analysis of threats and opportunities, we can presume that either significant improvement or basics of planning and scheduling reveals the patterns of the final draft. Therefore, the concept of the crucial component can be treated as the only solution. +One should, nevertheless, consider that the exceptional results of the diverse sources of information gives an overview of the production cycle. It should rather be regarded as an integral part of the direct access to key resources. +Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development. Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor. It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory. +In respect that the structure of the sufficient amount poses problems and challenges for both the set of related commands and controls and the ability bias."; + + [Benchmark] + public TextLayout BuildTextLayout() => new TextLayout(Text, Typeface.Default, 12d, Brushes.Black); + + private const string Emojis = @"😀 😁 😂 🤣 😃 😄 😅 😆 😉 😊 😋 😎 😍 😘 🥰 😗 😙 😚 ☺️ 🙂 🤗 🤩 🤔 🤨 😐 😑 😶 🙄 😏 😣 😥 😮 🤐 😯 😪 😫 😴 😌 😛 😜 😝 🤤 😒 😓 😔 😕 🙃 🤑 😲 ☹️ 🙁 😖 😞 😟 😤 😢 😭 😦 😧 😨 😩 🤯 😬 😰 😱 🥵 🥶 😳 🤪 😵 😡 😠 🤬 😷 🤒 🤕 🤢 🤮 🤧 😇 🤠 🤡 🥳 🥴 🥺 🤥 🤫 🤭 🧐 🤓 😈 👿 👹 👺 💀 👻 👽 🤖 💩 😺 😸 😹 😻 😼 😽 🙀 😿 😾 +👶 👧 🧒 👦 👩 🧑 👨 👵 🧓 👴 👲 👳‍♀️ 👳‍♂️ 🧕 🧔 👱‍♂️ 👱‍♀️ 👨‍🦰 👩‍🦰 👨‍🦱 👩‍🦱 👨‍🦲 👩‍🦲 👨‍🦳 👩‍🦳 🦸‍♀️ 🦸‍♂️ 🦹‍♀️ 🦹‍♂️ 👮‍♀️ 👮‍♂️ 👷‍♀️ 👷‍♂️ 💂‍♀️ 💂‍♂️ 🕵️‍♀️ 🕵️‍♂️ 👩‍⚕️ 👨‍⚕️ 👩‍🌾 👨‍🌾 👩‍🍳 👨‍🍳 👩‍🎓 👨‍🎓 👩‍🎤 👨‍🎤 👩‍🏫 👨‍🏫 👩‍🏭 👨‍🏭 👩‍💻 👨‍💻 👩‍💼 👨‍💼 👩‍🔧 👨‍🔧 👩‍🔬 👨‍🔬 👩‍🎨 👨‍🎨 👩‍🚒 👨‍🚒 👩‍✈️ 👨‍✈️ 👩‍🚀 👨‍🚀 👩‍⚖️ 👨‍⚖️ 👰 🤵 👸 🤴 🤶 🎅 🧙‍♀️ 🧙‍♂️ 🧝‍♀️ 🧝‍♂️ 🧛‍♀️ 🧛‍♂️ 🧟‍♀️ 🧟‍♂️ 🧞‍♀️ 🧞‍♂️ 🧜‍♀️ 🧜‍♂️ 🧚‍♀️ 🧚‍♂️ 👼 🤰 🤱 🙇‍♀️ 🙇‍♂️ 💁‍♀️ 💁‍♂️ 🙅‍♀️ 🙅‍♂️ 🙆‍♀️ 🙆‍♂️ 🙋‍♀️ 🙋‍♂️ 🤦‍♀️ 🤦‍♂️ 🤷‍♀️ 🤷‍♂️ 🙎‍♀️ 🙎‍♂️ 🙍‍♀️ 🙍‍♂️ 💇‍♀️ 💇‍♂️ 💆‍♀️ 💆‍♂️ 🧖‍♀️ 🧖‍♂️ 💅 🤳 💃 🕺 👯‍♀️ 👯‍♂️ 🕴 🚶‍♀️ 🚶‍♂️ 🏃‍♀️ 🏃‍♂️ 👫 👭 👬 💑 👩‍❤️‍👩 👨‍❤️‍👨 💏 👩‍❤️‍💋‍👩 👨‍❤️‍💋‍👨 👪 👨‍👩‍👧 👨‍👩‍👧‍👦 👨‍👩‍👦‍👦 👨‍👩‍👧‍👧 👩‍👩‍👦 👩‍👩‍👧 👩‍👩‍👧‍👦 👩‍👩‍👦‍👦 👩‍👩‍👧‍👧 👨‍👨‍👦 👨‍👨‍👧 👨‍👨‍👧‍👦 👨‍👨‍👦‍👦 👨‍👨‍👧‍👧 👩‍👦 👩‍👧 👩‍👧‍👦 👩‍👦‍👦 👩‍👧‍👧 👨‍👦 👨‍👧 👨‍👧‍👦 👨‍👦‍👦 👨‍👧‍👧 🤲 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤟 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🦵 🦶 🖕 ✍️ 🙏 💍 💄 💋 👄 👅 👂 👃 👣 👁 👀 🧠 🦴 🦷 🗣 👤 👥 +🧥 👚 👕 👖 👔 👗 👙 👘 👠 👡 👢 👞 👟 🥾 🥿 🧦 🧤 🧣 🎩 🧢 👒 🎓 ⛑ 👑 👝 👛 👜 💼 🎒 👓 🕶 🥽 🥼 🌂 🧵 🧶 +👶🏻 👦🏻 👧🏻 👨🏻 👩🏻 👱🏻‍♀️ 👱🏻 👴🏻 👵🏻 👲🏻 👳🏻‍♀️ 👳🏻 👮🏻‍♀️ 👮🏻 👷🏻‍♀️ 👷🏻 💂🏻‍♀️ 💂🏻 🕵🏻‍♀️ 🕵🏻 👩🏻‍⚕️ 👨🏻‍⚕️ 👩🏻‍🌾 👨🏻‍🌾 👩🏻‍🍳 👨🏻‍🍳 👩🏻‍🎓 👨🏻‍🎓 👩🏻‍🎤 👨🏻‍🎤 👩🏻‍🏫 👨🏻‍🏫 👩🏻‍🏭 👨🏻‍🏭 👩🏻‍💻 👨🏻‍💻 👩🏻‍💼 👨🏻‍💼 👩🏻‍🔧 👨🏻‍🔧 👩🏻‍🔬 👨🏻‍🔬 👩🏻‍🎨 👨🏻‍🎨 👩🏻‍🚒 👨🏻‍🚒 👩🏻‍✈️ 👨🏻‍✈️ 👩🏻‍🚀 👨🏻‍🚀 👩🏻‍⚖️ 👨🏻‍⚖️ 🤶🏻 🎅🏻 👸🏻 🤴🏻 👰🏻 🤵🏻 👼🏻 🤰🏻 🙇🏻‍♀️ 🙇🏻 💁🏻 💁🏻‍♂️ 🙅🏻 🙅🏻‍♂️ 🙆🏻 🙆🏻‍♂️ 🙋🏻 🙋🏻‍♂️ 🤦🏻‍♀️ 🤦🏻‍♂️ 🤷🏻‍♀️ 🤷🏻‍♂️ 🙎🏻 🙎🏻‍♂️ 🙍🏻 🙍🏻‍♂️ 💇🏻 💇🏻‍♂️ 💆🏻 💆🏻‍♂️ 🕴🏻 💃🏻 🕺🏻 🚶🏻‍♀️ 🚶🏻 🏃🏻‍♀️ 🏃🏻 🤲🏻 👐🏻 🙌🏻 👏🏻 🙏🏻 👍🏻 👎🏻 👊🏻 ✊🏻 🤛🏻 🤜🏻 🤞🏻 ✌🏻 🤟🏻 🤘🏻 👌🏻 👈🏻 👉🏻 👆🏻 👇🏻 ☝🏻 ✋🏻 🤚🏻 🖐🏻 🖖🏻 👋🏻 🤙🏻 💪🏻 🖕🏻 ✍🏻 🤳🏻 💅🏻 👂🏻 👃🏻 +👶🏼 👦🏼 👧🏼 👨🏼 👩🏼 👱🏼‍♀️ 👱🏼 👴🏼 👵🏼 👲🏼 👳🏼‍♀️ 👳🏼 👮🏼‍♀️ 👮🏼 👷🏼‍♀️ 👷🏼 💂🏼‍♀️ 💂🏼 🕵🏼‍♀️ 🕵🏼 👩🏼‍⚕️ 👨🏼‍⚕️ 👩🏼‍🌾 👨🏼‍🌾 👩🏼‍🍳 👨🏼‍🍳 👩🏼‍🎓 👨🏼‍🎓 👩🏼‍🎤 👨🏼‍🎤 👩🏼‍🏫 👨🏼‍🏫 👩🏼‍🏭 👨🏼‍🏭 👩🏼‍💻 👨🏼‍💻 👩🏼‍💼 👨🏼‍💼 👩🏼‍🔧 👨🏼‍🔧 👩🏼‍🔬 👨🏼‍🔬 👩🏼‍🎨 👨🏼‍🎨 👩🏼‍🚒 👨🏼‍🚒 👩🏼‍✈️ 👨🏼‍✈️ 👩🏼‍🚀 👨🏼‍🚀 👩🏼‍⚖️ 👨🏼‍⚖️ 🤶🏼 🎅🏼 👸🏼 🤴🏼 👰🏼 🤵🏼 👼🏼 🤰🏼 🙇🏼‍♀️ 🙇🏼 💁🏼 💁🏼‍♂️ 🙅🏼 🙅🏼‍♂️ 🙆🏼 🙆🏼‍♂️ 🙋🏼 🙋🏼‍♂️ 🤦🏼‍♀️ 🤦🏼‍♂️ 🤷🏼‍♀️ 🤷🏼‍♂️ 🙎🏼 🙎🏼‍♂️ 🙍🏼 🙍🏼‍♂️ 💇🏼 💇🏼‍♂️ 💆🏼 💆🏼‍♂️ 🕴🏼 💃🏼 🕺🏼 🚶🏼‍♀️ 🚶🏼 🏃🏼‍♀️ 🏃🏼 🤲🏼 👐🏼 🙌🏼 👏🏼 🙏🏼 👍🏼 👎🏼 👊🏼 ✊🏼 🤛🏼 🤜🏼 🤞🏼 ✌🏼 🤟🏼 🤘🏼 👌🏼 👈🏼 👉🏼 👆🏼 👇🏼 ☝🏼 ✋🏼 🤚🏼 🖐🏼 🖖🏼 👋🏼 🤙🏼 💪🏼 🖕🏼 ✍🏼 🤳🏼 💅🏼 👂🏼 👃🏼 +👶🏽 👦🏽 👧🏽 👨🏽 👩🏽 👱🏽‍♀️ 👱🏽 👴🏽 👵🏽 👲🏽 👳🏽‍♀️ 👳🏽 👮🏽‍♀️ 👮🏽 👷🏽‍♀️ 👷🏽 💂🏽‍♀️ 💂🏽 🕵🏽‍♀️ 🕵🏽 👩🏽‍⚕️ 👨🏽‍⚕️ 👩🏽‍🌾 👨🏽‍🌾 👩🏽‍🍳 👨🏽‍🍳 👩🏽‍🎓 👨🏽‍🎓 👩🏽‍🎤 👨🏽‍🎤 👩🏽‍🏫 👨🏽‍🏫 👩🏽‍🏭 👨🏽‍🏭 👩🏽‍💻 👨🏽‍💻 👩🏽‍💼 👨🏽‍💼 👩🏽‍🔧 👨🏽‍🔧 👩🏽‍🔬 👨🏽‍🔬 👩🏽‍🎨 👨🏽‍🎨 👩🏽‍🚒 👨🏽‍🚒 👩🏽‍✈️ 👨🏽‍✈️ 👩🏽‍🚀 👨🏽‍🚀 👩🏽‍⚖️ 👨🏽‍⚖️ 🤶🏽 🎅🏽 👸🏽 🤴🏽 👰🏽 🤵🏽 👼🏽 🤰🏽 🙇🏽‍♀️ 🙇🏽 💁🏽 💁🏽‍♂️ 🙅🏽 🙅🏽‍♂️ 🙆🏽 🙆🏽‍♂️ 🙋🏽 🙋🏽‍♂️ 🤦🏽‍♀️ 🤦🏽‍♂️ 🤷🏽‍♀️ 🤷🏽‍♂️ 🙎🏽 🙎🏽‍♂️ 🙍🏽 🙍🏽‍♂️ 💇🏽 💇🏽‍♂️ 💆🏽 💆🏽‍♂️ 🕴🏼 💃🏽 🕺🏽 🚶🏽‍♀️ 🚶🏽 🏃🏽‍♀️ 🏃🏽 🤲🏽 👐🏽 🙌🏽 👏🏽 🙏🏽 👍🏽 👎🏽 👊🏽 ✊🏽 🤛🏽 🤜🏽 🤞🏽 ✌🏽 🤟🏽 🤘🏽 👌🏽 👈🏽 👉🏽 👆🏽 👇🏽 ☝🏽 ✋🏽 🤚🏽 🖐🏽 🖖🏽 👋🏽 🤙🏽 💪🏽 🖕🏽 ✍🏽 🤳🏽 💅🏽 👂🏽 👃🏽 +👶🏾 👦🏾 👧🏾 👨🏾 👩🏾 👱🏾‍♀️ 👱🏾 👴🏾 👵🏾 👲🏾 👳🏾‍♀️ 👳🏾 👮🏾‍♀️ 👮🏾 👷🏾‍♀️ 👷🏾 💂🏾‍♀️ 💂🏾 🕵🏾‍♀️ 🕵🏾 👩🏾‍⚕️ 👨🏾‍⚕️ 👩🏾‍🌾 👨🏾‍🌾 👩🏾‍🍳 👨🏾‍🍳 👩🏾‍🎓 👨🏾‍🎓 👩🏾‍🎤 👨🏾‍🎤 👩🏾‍🏫 👨🏾‍🏫 👩🏾‍🏭 👨🏾‍🏭 👩🏾‍💻 👨🏾‍💻 👩🏾‍💼 👨🏾‍💼 👩🏾‍🔧 👨🏾‍🔧 👩🏾‍🔬 👨🏾‍🔬 👩🏾‍🎨 👨🏾‍🎨 👩🏾‍🚒 👨🏾‍🚒 👩🏾‍✈️ 👨🏾‍✈️ 👩🏾‍🚀 👨🏾‍🚀 👩🏾‍⚖️ 👨🏾‍⚖️ 🤶🏾 🎅🏾 👸🏾 🤴🏾 👰🏾 🤵🏾 👼🏾 🤰🏾 🙇🏾‍♀️ 🙇🏾 💁🏾 💁🏾‍♂️ 🙅🏾 🙅🏾‍♂️ 🙆🏾 🙆🏾‍♂️ 🙋🏾 🙋🏾‍♂️ 🤦🏾‍♀️ 🤦🏾‍♂️ 🤷🏾‍♀️ 🤷🏾‍♂️ 🙎🏾 🙎🏾‍♂️ 🙍🏾 🙍🏾‍♂️ 💇🏾 💇🏾‍♂️ 💆🏾 💆🏾‍♂️ 🕴🏾 💃🏾 🕺🏾 🚶🏾‍♀️ 🚶🏾 🏃🏾‍♀️ 🏃🏾 🤲🏾 👐🏾 🙌🏾 👏🏾 🙏🏾 👍🏾 👎🏾 👊🏾 ✊🏾 🤛🏾 🤜🏾 🤞🏾 ✌🏾 🤟🏾 🤘🏾 👌🏾 👈🏾 👉🏾 👆🏾 👇🏾 ☝🏾 ✋🏾 🤚🏾 🖐🏾 🖖🏾 👋🏾 🤙🏾 💪🏾 🖕🏾 ✍🏾 🤳🏾 💅🏾 👂🏾 👃🏾 +👶🏿 👦🏿 👧🏿 👨🏿 👩🏿 👱🏿‍♀️ 👱🏿 👴🏿 👵🏿 👲🏿 👳🏿‍♀️ 👳🏿 👮🏿‍♀️ 👮🏿 👷🏿‍♀️ 👷🏿 💂🏿‍♀️ 💂🏿 🕵🏿‍♀️ 🕵🏿 👩🏿‍⚕️ 👨🏿‍⚕️ 👩🏿‍🌾 👨🏿‍🌾 👩🏿‍🍳 👨🏿‍🍳 👩🏿‍🎓 👨🏿‍🎓 👩🏿‍🎤 👨🏿‍🎤 👩🏿‍🏫 👨🏿‍🏫 👩🏿‍🏭 👨🏿‍🏭 👩🏿‍💻 👨🏿‍💻 👩🏿‍💼 👨🏿‍💼 👩🏿‍🔧 👨🏿‍🔧 👩🏿‍🔬 👨🏿‍🔬 👩🏿‍🎨 👨🏿‍🎨 👩🏿‍🚒 👨🏿‍🚒 👩🏿‍✈️ 👨🏿‍✈️ 👩🏿‍🚀 👨🏿‍🚀 👩🏿‍⚖️ 👨🏿‍⚖️ 🤶🏿 🎅🏿 👸🏿 🤴🏿 👰🏿 🤵🏿 👼🏿 🤰🏿 🙇🏿‍♀️ 🙇🏿 💁🏿 💁🏿‍♂️ 🙅🏿 🙅🏿‍♂️ 🙆🏿 🙆🏿‍♂️ 🙋🏿 🙋🏿‍♂️ 🤦🏿‍♀️ 🤦🏿‍♂️ 🤷🏿‍♀️ 🤷🏿‍♂️ 🙎🏿 🙎🏿‍♂️ 🙍🏿 🙍🏿‍♂️ 💇🏿 💇🏿‍♂️ 💆🏿 💆🏿‍♂️ 🕴🏿 💃🏿 🕺🏿 🚶🏿‍♀️ 🚶🏿 🏃🏿‍♀️ 🏃🏿 🤲🏿 👐🏿 🙌🏿 👏🏿 🙏🏿 👍🏿 👎🏿 👊🏿 ✊🏿 🤛🏿 🤜🏿 🤞🏿 ✌🏿 🤟🏿 🤘🏿 👌🏿 👈🏿 👉🏿 👆🏿 👇🏿 ☝🏿 ✋🏿 🤚🏿 🖐🏿 🖖🏿 👋🏿 🤙🏿 💪🏿 🖕🏿 ✍🏿 🤳🏿 💅🏿 👂🏿 👃🏿 +🐶 🐱 🐭 🐹 🐰 🦊 🦝 🐻 🐼 🦘 🦡 🐨 🐯 🦁 🐮 🐷 🐽 🐸 🐵 🙈 🙉 🙊 🐒 🐔 🐧 🐦 🐤 🐣 🐥 🦆 🦢 🦅 🦉 🦚 🦜 🦇 🐺 🐗 🐴 🦄 🐝 🐛 🦋 🐌 🐚 🐞 🐜 🦗 🕷 🕸 🦂 🦟 🦠 🐢 🐍 🦎 🦖 🦕 🐙 🦑 🦐 🦀 🐡 🐠 🐟 🐬 🐳 🐋 🦈 🐊 🐅 🐆 🦓 🦍 🐘 🦏 🦛 🐪 🐫 🦙 🦒 🐃 🐂 🐄 🐎 🐖 🐏 🐑 🐐 🦌 🐕 🐩 🐈 🐓 🦃 🕊 🐇 🐁 🐀 🐿 🦔 🐾 🐉 🐲 🌵 🎄 🌲 🌳 🌴 🌱 🌿 ☘️ 🍀 🎍 🎋 🍃 🍂 🍁 🍄 🌾 💐 🌷 🌹 🥀 🌺 🌸 🌼 🌻 🌞 🌝 🌛 🌜 🌚 🌕 🌖 🌗 🌘 🌑 🌒 🌓 🌔 🌙 🌎 🌍 🌏 💫 ⭐️ 🌟 ✨ ⚡️ ☄️ 💥 🔥 🌪 🌈 ☀️ 🌤 ⛅️ 🌥 ☁️ 🌦 🌧 ⛈ 🌩 🌨 ❄️ ☃️ ⛄️ 🌬 💨 💧 💦 ☔️ ☂️ 🌊 🌫 +🍏 🍎 🍐 🍊 🍋 🍌 🍉 🍇 🍓 🍈 🍒 🍑 🍍 🥭 🥥 🥝 🍅 🍆 🥑 🥦 🥒 🥬 🌶 🌽 🥕 🥔 🍠 🥐 🍞 🥖 🥨 🥯 🧀 🥚 🍳 🥞 🥓 🥩 🍗 🍖 🌭 🍔 🍟 🍕 🥪 🥙 🌮 🌯 🥗 🥘 🥫 🍝 🍜 🍲 🍛 🍣 🍱 🥟 🍤 🍙 🍚 🍘 🍥 🥮 🥠 🍢 🍡 🍧 🍨 🍦 🥧 🍰 🎂 🍮 🍭 🍬 🍫 🍿 🧂 🍩 🍪 🌰 🥜 🍯 🥛 🍼 ☕️ 🍵 🥤 🍶 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🍾 🥄 🍴 🍽 🥣 🥡 🥢 +⚽️ 🏀 🏈 ⚾️ 🥎 🏐 🏉 🎾 🥏 🎱 🏓 🏸 🥅 🏒 🏑 🥍 🏏 ⛳️ 🏹 🎣 🥊 🥋 🎽 ⛸ 🥌 🛷 🛹 🎿 ⛷ 🏂 🏋️‍♀️ 🏋🏻‍♀️ 🏋🏼‍♀️ 🏋🏽‍♀️ 🏋🏾‍♀️ 🏋🏿‍♀️ 🏋️‍♂️ 🏋🏻‍♂️ 🏋🏼‍♂️ 🏋🏽‍♂️ 🏋🏾‍♂️ 🏋🏿‍♂️ 🤼‍♀️ 🤼‍♂️ 🤸‍♀️ 🤸🏻‍♀️ 🤸🏼‍♀️ 🤸🏽‍♀️ 🤸🏾‍♀️ 🤸🏿‍♀️ 🤸‍♂️ 🤸🏻‍♂️ 🤸🏼‍♂️ 🤸🏽‍♂️ 🤸🏾‍♂️ 🤸🏿‍♂️ ⛹️‍♀️ ⛹🏻‍♀️ ⛹🏼‍♀️ ⛹🏽‍♀️ ⛹🏾‍♀️ ⛹🏿‍♀️ ⛹️‍♂️ ⛹🏻‍♂️ ⛹🏼‍♂️ ⛹🏽‍♂️ ⛹🏾‍♂️ ⛹🏿‍♂️ 🤺 🤾‍♀️ 🤾🏻‍♀️ 🤾🏼‍♀️ 🤾🏾‍♀️ 🤾🏾‍♀️ 🤾🏿‍♀️ 🤾‍♂️ 🤾🏻‍♂️ 🤾🏼‍♂️ 🤾🏽‍♂️ 🤾🏾‍♂️ 🤾🏿‍♂️ 🏌️‍♀️ 🏌🏻‍♀️ 🏌🏼‍♀️ 🏌🏽‍♀️ 🏌🏾‍♀️ 🏌🏿‍♀️ 🏌️‍♂️ 🏌🏻‍♂️ 🏌🏼‍♂️ 🏌🏽‍♂️ 🏌🏾‍♂️ 🏌🏿‍♂️ 🏇 🏇🏻 🏇🏼 🏇🏽 🏇🏾 🏇🏿 🧘‍♀️ 🧘🏻‍♀️ 🧘🏼‍♀️ 🧘🏽‍♀️ 🧘🏾‍♀️ 🧘🏿‍♀️ 🧘‍♂️ 🧘🏻‍♂️ 🧘🏼‍♂️ 🧘🏽‍♂️ 🧘🏾‍♂️ 🧘🏿‍♂️ 🏄‍♀️ 🏄🏻‍♀️ 🏄🏼‍♀️ 🏄🏽‍♀️ 🏄🏾‍♀️ 🏄🏿‍♀️ 🏄‍♂️ 🏄🏻‍♂️ 🏄🏼‍♂️ 🏄🏽‍♂️ 🏄🏾‍♂️ 🏄🏿‍♂️ 🏊‍♀️ 🏊🏻‍♀️ 🏊🏼‍♀️ 🏊🏽‍♀️ 🏊🏾‍♀️ 🏊🏿‍♀️ 🏊‍♂️ 🏊🏻‍♂️ 🏊🏼‍♂️ 🏊🏽‍♂️ 🏊🏾‍♂️ 🏊🏿‍♂️ 🤽‍♀️ 🤽🏻‍♀️ 🤽🏼‍♀️ 🤽🏽‍♀️ 🤽🏾‍♀️ 🤽🏿‍♀️ 🤽‍♂️ 🤽🏻‍♂️ 🤽🏼‍♂️ 🤽🏽‍♂️ 🤽🏾‍♂️ 🤽🏿‍♂️ 🚣‍♀️ 🚣🏻‍♀️ 🚣🏼‍♀️ 🚣🏽‍♀️ 🚣🏾‍♀️ 🚣🏿‍♀️ 🚣‍♂️ 🚣🏻‍♂️ 🚣🏼‍♂️ 🚣🏽‍♂️ 🚣🏾‍♂️ 🚣🏿‍♂️ 🧗‍♀️ 🧗🏻‍♀️ 🧗🏼‍♀️ 🧗🏽‍♀️ 🧗🏾‍♀️ 🧗🏿‍♀️ 🧗‍♂️ 🧗🏻‍♂️ 🧗🏼‍♂️ 🧗🏽‍♂️ 🧗🏾‍♂️ 🧗🏿‍♂️ 🚵‍♀️ 🚵🏻‍♀️ 🚵🏼‍♀️ 🚵🏽‍♀️ 🚵🏾‍♀️ 🚵🏿‍♀️ 🚵‍♂️ 🚵🏻‍♂️ 🚵🏼‍♂️ 🚵🏽‍♂️ 🚵🏾‍♂️ 🚵🏿‍♂️ 🚴‍♀️ 🚴🏻‍♀️ 🚴🏼‍♀️ 🚴🏽‍♀️ 🚴🏾‍♀️ 🚴🏿‍♀️ 🚴‍♂️ 🚴🏻‍♂️ 🚴🏼‍♂️ 🚴🏽‍♂️ 🚴🏾‍♂️ 🚴🏿‍♂️ 🏆 🥇 🥈 🥉 🏅 🎖 🏵 🎗 🎫 🎟 🎪 🤹‍♀️ 🤹🏻‍♀️ 🤹🏼‍♀️ 🤹🏽‍♀️ 🤹🏾‍♀️ 🤹🏿‍♀️ 🤹‍♂️ 🤹🏻‍♂️ 🤹🏼‍♂️ 🤹🏽‍♂️ 🤹🏾‍♂️ 🤹🏿‍♂️ 🎭 🎨 🎬 🎤 🎧 🎼 🎹 🥁 🎷 🎺 🎸 🎻 🎲 🧩 ♟ 🎯 🎳 🎮 🎰 +🚗 🚕 🚙 🚌 🚎 🏎 🚓 🚑 🚒 🚐 🚚 🚛 🚜 🛴 🚲 🛵 🏍 🚨 🚔 🚍 🚘 🚖 🚡 🚠 🚟 🚃 🚋 🚞 🚝 🚄 🚅 🚈 🚂 🚆 🚇 🚊 🚉 ✈️ 🛫 🛬 🛩 💺 🛰 🚀 🛸 🚁 🛶 ⛵️ 🚤 🛥 🛳 ⛴ 🚢 ⚓️ ⛽️ 🚧 🚦 🚥 🚏 🗺 🗿 🗽 🗼 🏰 🏯 🏟 🎡 🎢 🎠 ⛲️ ⛱ 🏖 🏝 🏜 🌋 ⛰ 🏔 🗻 🏕 ⛺️ 🏠 🏡 🏘 🏚 🏗 🏭 🏢 🏬 🏣 🏤 🏥 🏦 🏨 🏪 🏫 🏩 💒 🏛 ⛪️ 🕌 🕍 🕋 ⛩ 🛤 🛣 🗾 🎑 🏞 🌅 🌄 🌠 🎇 🎆 🌇 🌆 🏙 🌃 🌌 🌉 🌁 +⌚️ 📱 📲 💻 ⌨️ 🖥 🖨 🖱 🖲 🕹 🗜 💽 💾 💿 📀 📼 📷 📸 📹 🎥 📽 🎞 📞 ☎️ 📟 📠 📺 📻 🎙 🎚 🎛 ⏱ ⏲ ⏰ 🕰 ⌛️ ⏳ 📡 🔋 🔌 💡 🔦 🕯 🗑 🛢 💸 💵 💴 💶 💷 💰 💳 🧾 💎 ⚖️ 🔧 🔨 ⚒ 🛠 ⛏ 🔩 ⚙️ ⛓ 🔫 💣 🔪 🗡 ⚔️ 🛡 🚬 ⚰️ ⚱️ 🏺 🧭 🧱 🔮 🧿 🧸 📿 💈 ⚗️ 🔭 🧰 🧲 🧪 🧫 🧬 🧯 🔬 🕳 💊 💉 🌡 🚽 🚰 🚿 🛁 🛀 🛀🏻 🛀🏼 🛀🏽 🛀🏾 🛀🏿 🧴 🧵 🧶 🧷 🧹 🧺 🧻 🧼 🧽 🛎 🔑 🗝 🚪 🛋 🛏 🛌 🖼 🛍 🧳 🛒 🎁 🎈 🎏 🎀 🎊 🎉 🧨 🎎 🏮 🎐 🧧 ✉️ 📩 📨 📧 💌 📥 📤 📦 🏷 📪 📫 📬 📭 📮 📯 📜 📃 📄 📑 📊 📈 📉 🗒 🗓 📆 📅 📇 🗃 🗳 🗄 📋 📁 📂 🗂 🗞 📰 📓 📔 📒 📕 📗 📘 📙 📚 📖 🔖 🔗 📎 🖇 📐 📏 📌 📍 ✂️ 🖊 🖋 ✒️ 🖌 🖍 📝 ✏️ 🔍 🔎 🔏 🔐 🔒 🔓 +❤️ 🧡 💛 💚 💙 💜 🖤 💔 ❣️ 💕 💞 💓 💗 💖 💘 💝 💟 ☮️ ✝️ ☪️ 🕉 ☸️ ✡️ 🔯 🕎 ☯️ ☦️ 🛐 ⛎ ♈️ ♉️ ♊️ ♋️ ♌️ ♍️ ♎️ ♏️ ♐️ ♑️ ♒️ ♓️ 🆔 ⚛️ 🉑 ☢️ ☣️ 📴 📳 🈶 🈚️ 🈸 🈺 🈷️ ✴️ 🆚 💮 🉐 ㊙️ ㊗️ 🈴 🈵 🈹 🈲 🅰️ 🅱️ 🆎 🆑 🅾️ 🆘 ❌ ⭕️ 🛑 ⛔️ 📛 🚫 💯 💢 ♨️ 🚷 🚯 🚳 🚱 🔞 📵 🚭 ❗️ ❕ ❓ ❔ ‼️ ⁉️ 🔅 🔆 〽️ ⚠️ 🚸 🔱 ⚜️ 🔰 ♻️ ✅ 🈯️ 💹 ❇️ ✳️ ❎ 🌐 💠 Ⓜ️ 🌀 💤 🏧 🚾 ♿️ 🅿️ 🈳 🈂️ 🛂 🛃 🛄 🛅 🚹 🚺 🚼 🚻 🚮 🎦 📶 🈁 🔣 ℹ️ 🔤 🔡 🔠 🆖 🆗 🆙 🆒 🆕 🆓 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 🔢 #️⃣ *️⃣ ⏏️ ▶️ ⏸ ⏯ ⏹ ⏺ ⏭ ⏮ ⏩ ⏪ ⏫ ⏬ ◀️ 🔼 🔽 ➡️ ⬅️ ⬆️ ⬇️ ↗️ ↘️ ↙️ ↖️ ↕️ ↔️ ↪️ ↩️ ⤴️ ⤵️ 🔀 🔁 🔂 🔄 🔃 🎵 🎶 ➕ ➖ ➗ ✖️ ♾ 💲 💱 ™️ ©️ ®️ 〰️ ➰ ➿ 🔚 🔙 🔛 🔝 🔜 ✔️ ☑️ 🔘 ⚪️ ⚫️ 🔴 🔵 🔺 🔻 🔸 🔹 🔶 🔷 🔳 🔲 ▪️ ▫️ ◾️ ◽️ ◼️ ◻️ ⬛️ ⬜️ 🔈 🔇 🔉 🔊 🔔 🔕 📣 📢 👁‍🗨 💬 💭 🗯 ♠️ ♣️ ♥️ ♦️ 🃏 🎴 🀄️ 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧 +🏳️ 🏴 🏁 🚩 🏳️‍🌈 🏴‍☠️ 🇦🇫 🇦🇽 🇦🇱 🇩🇿 🇦🇸 🇦🇩 🇦🇴 🇦🇮 🇦🇶 🇦🇬 🇦🇷 🇦🇲 🇦🇼 🇦🇺 🇦🇹 🇦🇿 🇧🇸 🇧🇭 🇧🇩 🇧🇧 🇧🇾 🇧🇪 🇧🇿 🇧🇯 🇧🇲 🇧🇹 🇧🇴 🇧🇦 🇧🇼 🇧🇷 🇮🇴 🇻🇬 🇧🇳 🇧🇬 🇧🇫 🇧🇮 🇰🇭 🇨🇲 🇨🇦 🇮🇨 🇨🇻 🇧🇶 🇰🇾 🇨🇫 🇹🇩 🇨🇱 🇨🇳 🇨🇽 🇨🇨 🇨🇴 🇰🇲 🇨🇬 🇨🇩 🇨🇰 🇨🇷 🇨🇮 🇭🇷 🇨🇺 🇨🇼 🇨🇾 🇨🇿 🇩🇰 🇩🇯 🇩🇲 🇩🇴 🇪🇨 🇪🇬 🇸🇻 🇬🇶 🇪🇷 🇪🇪 🇪🇹 🇪🇺 🇫🇰 🇫🇴 🇫🇯 🇫🇮 🇫🇷 🇬🇫 🇵🇫 🇹🇫 🇬🇦 🇬🇲 🇬🇪 🇩🇪 🇬🇭 🇬🇮 🇬🇷 🇬🇱 🇬🇩 🇬🇵 🇬🇺 🇬🇹 🇬🇬 🇬🇳 🇬🇼 🇬🇾 🇭🇹 🇭🇳 🇭🇰 🇭🇺 🇮🇸 🇮🇳 🇮🇩 🇮🇷 🇮🇶 🇮🇪 🇮🇲 🇮🇱 🇮🇹 🇯🇲 🇯🇵 🎌 🇯🇪 🇯🇴 🇰🇿 🇰🇪 🇰🇮 🇽🇰 🇰🇼 🇰🇬 🇱🇦 🇱🇻 🇱🇧 🇱🇸 🇱🇷 🇱🇾 🇱🇮 🇱🇹 🇱🇺 🇲🇴 🇲🇰 🇲🇬 🇲🇼 🇲🇾 🇲🇻 🇲🇱 🇲🇹 🇲🇭 🇲🇶 🇲🇷 🇲🇺 🇾🇹 🇲🇽 🇫🇲 🇲🇩 🇲🇨 🇲🇳 🇲🇪 🇲🇸 🇲🇦 🇲🇿 🇲🇲 🇳🇦 🇳🇷 🇳🇵 🇳🇱 🇳🇨 🇳🇿 🇳🇮 🇳🇪 🇳🇬 🇳🇺 🇳🇫 🇰🇵 🇲🇵 🇳🇴 🇴🇲 🇵🇰 🇵🇼 🇵🇸 🇵🇦 🇵🇬 🇵🇾 🇵🇪 🇵🇭 🇵🇳 🇵🇱 🇵🇹 🇵🇷 🇶🇦 🇷🇪 🇷🇴 🇷🇺 🇷🇼 🇼🇸 🇸🇲 🇸🇦 🇸🇳 🇷🇸 🇸🇨 🇸🇱 🇸🇬 🇸🇽 🇸🇰 🇸🇮 🇬🇸 🇸🇧 🇸🇴 🇿🇦 🇰🇷 🇸🇸 🇪🇸 🇱🇰 🇧🇱 🇸🇭 🇰🇳 🇱🇨 🇵🇲 🇻🇨 🇸🇩 🇸🇷 🇸🇿 🇸🇪 🇨🇭 🇸🇾 🇹🇼 🇹🇯 🇹🇿 🇹🇭 🇹🇱 🇹🇬 🇹🇰 🇹🇴 🇹🇹 🇹🇳 🇹🇷 🇹🇲 🇹🇨 🇹🇻 🇻🇮 🇺🇬 🇺🇦 🇦🇪 🇬🇧 🏴󠁧󠁢󠁥󠁮󠁧󠁿 🏴󠁧󠁢󠁳󠁣󠁴󠁿 🏴󠁧󠁢󠁷󠁬󠁳󠁿 🇺🇳 🇺🇸 🇺🇾 🇺🇿 🇻🇺 🇻🇦 🇻🇪 🇻🇳 🇼🇫 🇪🇭 🇾🇪 🇿🇲 🇿🇼 +🥱 🤏 🦾 🦿 🦻 🧏 🧏‍♂️ 🧏‍♀️ 🧍 🧍‍♂️ 🧍‍♀️ 🧎 🧎‍♂️ 🧎‍♀️ 👨‍🦯 👩‍🦯 👨‍🦼 👩‍🦼 👨‍🦽 👩‍🦽 🦧 🦮 🐕‍🦺 🦥 🦦 🦨 🦩 🧄 🧅 🧇 🧆 🧈 🦪 🧃 🧉 🧊 🛕 🦽 🦼 🛺 🪂 🪐 🤿 🪀 🪁 🦺 🥻 🩱 🩲 🩳 🩰 🪕 🪔 🪓 🦯 🩸 🩹 🩺 🪑 🪒 🤎 🤍 🟠 🟡 🟢 🟣 🟤 🟥 🟧 🟨 🟩 🟦 🟪 🟫"; + + [Benchmark] + public TextLayout BuildEmojisTextLayout() => new TextLayout(Emojis, Typeface.Default, 12d, Brushes.Black); + + public void Dispose() + { + _app?.Dispose(); + } +} diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 2790bd6096..7501bf21fe 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -153,7 +153,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { while (inner.MoveNext()) { - j += inner.Current.Text.Length; + j += inner.Current.Length; if (j + i > text.Length) { @@ -192,7 +192,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting inner = new GraphemeEnumerator(new CharacterBufferRange(text)); - i += outer.Current.Text.Length; + i += outer.Current.Length; } } } @@ -974,9 +974,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var grapheme = graphemeEnumerator.Current; - var textStyleOverrides = new[] { new ValueSpan(i, grapheme.Text.Length, new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Red)) }; + var textStyleOverrides = new[] { new ValueSpan(i, grapheme.Length, new GenericTextRunProperties(Typeface.Default, 12, foregroundBrush: Brushes.Red)) }; - i += grapheme.Text.Length; + i += grapheme.Length; var layout = new TextLayout( text, From 8c30948e62dcf565d26dbda39020c5dbb15f096a Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 7 Jan 2023 04:59:46 +0300 Subject: [PATCH 181/213] [Text] optimize CharacterBufferReference indexator --- src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs index d76f212f26..86e44a5269 100644 --- a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs +++ b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs @@ -140,7 +140,7 @@ namespace Avalonia.Media.TextFormatting throw new ArgumentOutOfRangeException(nameof(index)); } #endif - return Span[index]; + return CharacterBufferReference.CharacterBuffer.Span[CharacterBufferReference.OffsetToFirstChar + index]; } } From 5bd28141b78d88bb72eb92fe4edb00451dc1e7d8 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 7 Jan 2023 05:07:27 +0300 Subject: [PATCH 182/213] [Text] minor optimize of CharacterBufferRange --- .../TextFormatting/CharacterBufferRange.cs | 29 +++++-------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs index 86e44a5269..599b35dd2c 100644 --- a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs +++ b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs @@ -147,12 +147,12 @@ namespace Avalonia.Media.TextFormatting /// /// Gets a reference to the character buffer /// - public CharacterBufferReference CharacterBufferReference { get; } + public readonly CharacterBufferReference CharacterBufferReference; /// /// Gets the number of characters in text source character store /// - public int Length { get; } + public readonly int Length; /// /// Gets a span from the character buffer range @@ -163,27 +163,18 @@ namespace Avalonia.Media.TextFormatting /// /// Gets the character memory buffer /// - internal ReadOnlyMemory CharacterBuffer - { - get { return CharacterBufferReference.CharacterBuffer; } - } + internal ReadOnlyMemory CharacterBuffer => CharacterBufferReference.CharacterBuffer; /// /// Gets the character offset relative to the beginning of buffer to /// the first character of the run /// - internal int OffsetToFirstChar - { - get { return CharacterBufferReference.OffsetToFirstChar; } - } + internal int OffsetToFirstChar => CharacterBufferReference.OffsetToFirstChar; /// /// Indicate whether the character buffer range is empty /// - internal bool IsEmpty - { - get { return CharacterBufferReference.CharacterBuffer.Length == 0 || Length <= 0; } - } + internal bool IsEmpty => CharacterBufferReference.CharacterBuffer.Length == 0 || Length <= 0; internal CharacterBufferRange Take(int length) { @@ -280,14 +271,8 @@ namespace Avalonia.Media.TextFormatting int IReadOnlyCollection.Count => Length; - public IEnumerator GetEnumerator() - { - return new ImmutableReadOnlyListStructEnumerator(this); - } + public IEnumerator GetEnumerator() => new ImmutableReadOnlyListStructEnumerator(this); - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } From ba9bf5d3641df0d75c780c4ef021b7762764f3d6 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 7 Jan 2023 05:13:50 +0300 Subject: [PATCH 183/213] [Text] Clone CodePoint ReadAt for Span --- .../Media/TextFormatting/Unicode/Codepoint.cs | 68 ++++++++++++++++++- .../Unicode/CodepointEnumerator.cs | 2 +- .../Unicode/GraphemeEnumerator.cs | 2 +- .../Avalonia.UnitTests/MockTextShaperImpl.cs | 2 +- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index e6408bcfa6..ecd0681e15 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; namespace Avalonia.Media.TextFormatting.Unicode @@ -222,6 +223,71 @@ namespace Avalonia.Media.TextFormatting.Unicode return new Codepoint(code); } + + /// + /// Reads the at specified position. + /// + /// The buffer to read from. + /// The index to read at. + /// The count of character that were read. + /// + public static Codepoint ReadAt(ReadOnlySpan text, int index, out int count) + { + count = 1; + + if (index >= text.Length) + { + return ReplacementCodepoint; + } + + var code = text[index]; + + ushort hi, low; + + //# High surrogate + if (0xD800 <= code && code <= 0xDBFF) + { + hi = code; + + if (index + 1 == text.Length) + { + return ReplacementCodepoint; + } + + low = text[index + 1]; + + if (0xDC00 <= low && low <= 0xDFFF) + { + count = 2; + return new Codepoint((uint)((hi - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)); + } + + return ReplacementCodepoint; + } + + //# Low surrogate + if (0xDC00 <= code && code <= 0xDFFF) + { + if (index == 0) + { + return ReplacementCodepoint; + } + + hi = text[index - 1]; + + low = code; + + if (0xD800 <= hi && hi <= 0xDBFF) + { + count = 2; + return new Codepoint((uint)((hi - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)); + } + + return ReplacementCodepoint; + } + + return new Codepoint(code); + } /// /// Returns if is between diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs index a2c36d9a13..a2e29812d3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs @@ -30,7 +30,7 @@ namespace Avalonia.Media.TextFormatting.Unicode return false; } - Current = Codepoint.ReadAt(_text, 0, out var count); + Current = Codepoint.ReadAt(_text.Span, 0, out var count); _text = _text.Skip(count); diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs index dc21e06813..2488b620e7 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs @@ -245,7 +245,7 @@ namespace Avalonia.Media.TextFormatting.Unicode if (CurrentCodeUnitOffset < _buffer.Length) { - CurrentCodepoint = Codepoint.ReadAt(_buffer, CurrentCodeUnitOffset, + CurrentCodepoint = Codepoint.ReadAt(_buffer.Span, CurrentCodeUnitOffset, out _codeUnitLengthOfCurrentScalar); } else diff --git a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs b/tests/Avalonia.UnitTests/MockTextShaperImpl.cs index 00bcef295a..d010f9ebf2 100644 --- a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs +++ b/tests/Avalonia.UnitTests/MockTextShaperImpl.cs @@ -18,7 +18,7 @@ namespace Avalonia.UnitTests { var glyphCluster = i + text.OffsetToFirstChar; - var codepoint = Codepoint.ReadAt(characterBufferRange, i, out var count); + var codepoint = Codepoint.ReadAt(characterBufferRange.Span, i, out var count); var glyphIndex = typeface.GetGlyph(codepoint); From 80972e5f5b287809caeabe0b8873c845061f7c34 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 7 Jan 2023 05:23:58 +0300 Subject: [PATCH 184/213] [Text] a bit better usage of Codepoint.ReadAt --- src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs | 2 +- .../Media/TextFormatting/Unicode/CodepointEnumerator.cs | 2 +- .../Media/TextFormatting/Unicode/GraphemeEnumerator.cs | 2 +- tests/Avalonia.UnitTests/MockTextShaperImpl.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs index ecd0681e15..8b9d1c1d02 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs @@ -231,7 +231,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The index to read at. /// The count of character that were read. /// - public static Codepoint ReadAt(ReadOnlySpan text, int index, out int count) + public static Codepoint ReadAt(CharacterBufferRange text, int index, out int count) { count = 1; diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs index a2e29812d3..a2c36d9a13 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/CodepointEnumerator.cs @@ -30,7 +30,7 @@ namespace Avalonia.Media.TextFormatting.Unicode return false; } - Current = Codepoint.ReadAt(_text.Span, 0, out var count); + Current = Codepoint.ReadAt(_text, 0, out var count); _text = _text.Skip(count); diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs index 2488b620e7..dc21e06813 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeEnumerator.cs @@ -245,7 +245,7 @@ namespace Avalonia.Media.TextFormatting.Unicode if (CurrentCodeUnitOffset < _buffer.Length) { - CurrentCodepoint = Codepoint.ReadAt(_buffer.Span, CurrentCodeUnitOffset, + CurrentCodepoint = Codepoint.ReadAt(_buffer, CurrentCodeUnitOffset, out _codeUnitLengthOfCurrentScalar); } else diff --git a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs b/tests/Avalonia.UnitTests/MockTextShaperImpl.cs index d010f9ebf2..00bcef295a 100644 --- a/tests/Avalonia.UnitTests/MockTextShaperImpl.cs +++ b/tests/Avalonia.UnitTests/MockTextShaperImpl.cs @@ -18,7 +18,7 @@ namespace Avalonia.UnitTests { var glyphCluster = i + text.OffsetToFirstChar; - var codepoint = Codepoint.ReadAt(characterBufferRange.Span, i, out var count); + var codepoint = Codepoint.ReadAt(characterBufferRange, i, out var count); var glyphIndex = typeface.GetGlyph(codepoint); From c9bf6310a8582afc10a504ae1b938267884a58f0 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 7 Jan 2023 14:18:25 +0300 Subject: [PATCH 185/213] [Text] revert fields to props in CharacterBufferRange --- .../Media/TextFormatting/CharacterBufferRange.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs index 599b35dd2c..f5d39e4371 100644 --- a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs +++ b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs @@ -147,12 +147,12 @@ namespace Avalonia.Media.TextFormatting /// /// Gets a reference to the character buffer /// - public readonly CharacterBufferReference CharacterBufferReference; + public CharacterBufferReference CharacterBufferReference { get; } /// /// Gets the number of characters in text source character store /// - public readonly int Length; + public int Length { get; } /// /// Gets a span from the character buffer range From 2565538fd30a9669198b2c611b9fcdabd602bac0 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sun, 8 Jan 2023 03:37:17 +0300 Subject: [PATCH 186/213] [Text] improve benchmarks. add BuildManySmallTexts benchmark --- .../Text/HugeTextLayout.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs index e1b3c6a99c..e696b976ad 100644 --- a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs +++ b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.UnitTests; @@ -10,9 +11,18 @@ namespace Avalonia.Benchmarks.Text; public class HugeTextLayout : IDisposable { private readonly IDisposable _app; + private string[] _manySmallStrings; + private static Random _rand = new Random(); + + private static string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&?%$@"; + return new string(Enumerable.Repeat(chars, length).Select(s => s[_rand.Next(s.Length)]).ToArray()); + } public HugeTextLayout() { + _manySmallStrings = Enumerable.Range(0, 1000).Select(x => RandomString(_rand.Next(2, 15))).ToArray(); _app = UnitTestApplication.Start( TestServices.StyledWindow.With( renderInterface: new NullRenderingPlatform(), @@ -29,11 +39,13 @@ One should, however, not forget that concentration of violations of the strategi In a loose sense concentration of the center of the critical thinking provides a deep insight into the emergency planning. The comparison is quite a powerful matter. Resulting from review or analysis of threats and opportunities, we can presume that either significant improvement or basics of planning and scheduling reveals the patterns of the final draft. Therefore, the concept of the crucial component can be treated as the only solution. One should, nevertheless, consider that the exceptional results of the diverse sources of information gives an overview of the production cycle. It should rather be regarded as an integral part of the direct access to key resources. -Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development. Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor. It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory. +Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development. +Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor. +It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory. In respect that the structure of the sufficient amount poses problems and challenges for both the set of related commands and controls and the ability bias."; [Benchmark] - public TextLayout BuildTextLayout() => new TextLayout(Text, Typeface.Default, 12d, Brushes.Black); + public TextLayout BuildTextLayout() => MakeLayout(Text); private const string Emojis = @"😀 😁 😂 🤣 😃 😄 😅 😆 😉 😊 😋 😎 😍 😘 🥰 😗 😙 😚 ☺️ 🙂 🤗 🤩 🤔 🤨 😐 😑 😶 🙄 😏 😣 😥 😮 🤐 😯 😪 😫 😴 😌 😛 😜 😝 🤤 😒 😓 😔 😕 🙃 🤑 😲 ☹️ 🙁 😖 😞 😟 😤 😢 😭 😦 😧 😨 😩 🤯 😬 😰 😱 🥵 🥶 😳 🤪 😵 😡 😠 🤬 😷 🤒 🤕 🤢 🤮 🤧 😇 🤠 🤡 🥳 🥴 🥺 🤥 🤫 🤭 🧐 🤓 😈 👿 👹 👺 💀 👻 👽 🤖 💩 😺 😸 😹 😻 😼 😽 🙀 😿 😾 👶 👧 🧒 👦 👩 🧑 👨 👵 🧓 👴 👲 👳‍♀️ 👳‍♂️ 🧕 🧔 👱‍♂️ 👱‍♀️ 👨‍🦰 👩‍🦰 👨‍🦱 👩‍🦱 👨‍🦲 👩‍🦲 👨‍🦳 👩‍🦳 🦸‍♀️ 🦸‍♂️ 🦹‍♀️ 🦹‍♂️ 👮‍♀️ 👮‍♂️ 👷‍♀️ 👷‍♂️ 💂‍♀️ 💂‍♂️ 🕵️‍♀️ 🕵️‍♂️ 👩‍⚕️ 👨‍⚕️ 👩‍🌾 👨‍🌾 👩‍🍳 👨‍🍳 👩‍🎓 👨‍🎓 👩‍🎤 👨‍🎤 👩‍🏫 👨‍🏫 👩‍🏭 👨‍🏭 👩‍💻 👨‍💻 👩‍💼 👨‍💼 👩‍🔧 👨‍🔧 👩‍🔬 👨‍🔬 👩‍🎨 👨‍🎨 👩‍🚒 👨‍🚒 👩‍✈️ 👨‍✈️ 👩‍🚀 👨‍🚀 👩‍⚖️ 👨‍⚖️ 👰 🤵 👸 🤴 🤶 🎅 🧙‍♀️ 🧙‍♂️ 🧝‍♀️ 🧝‍♂️ 🧛‍♀️ 🧛‍♂️ 🧟‍♀️ 🧟‍♂️ 🧞‍♀️ 🧞‍♂️ 🧜‍♀️ 🧜‍♂️ 🧚‍♀️ 🧚‍♂️ 👼 🤰 🤱 🙇‍♀️ 🙇‍♂️ 💁‍♀️ 💁‍♂️ 🙅‍♀️ 🙅‍♂️ 🙆‍♀️ 🙆‍♂️ 🙋‍♀️ 🙋‍♂️ 🤦‍♀️ 🤦‍♂️ 🤷‍♀️ 🤷‍♂️ 🙎‍♀️ 🙎‍♂️ 🙍‍♀️ 🙍‍♂️ 💇‍♀️ 💇‍♂️ 💆‍♀️ 💆‍♂️ 🧖‍♀️ 🧖‍♂️ 💅 🤳 💃 🕺 👯‍♀️ 👯‍♂️ 🕴 🚶‍♀️ 🚶‍♂️ 🏃‍♀️ 🏃‍♂️ 👫 👭 👬 💑 👩‍❤️‍👩 👨‍❤️‍👨 💏 👩‍❤️‍💋‍👩 👨‍❤️‍💋‍👨 👪 👨‍👩‍👧 👨‍👩‍👧‍👦 👨‍👩‍👦‍👦 👨‍👩‍👧‍👧 👩‍👩‍👦 👩‍👩‍👧 👩‍👩‍👧‍👦 👩‍👩‍👦‍👦 👩‍👩‍👧‍👧 👨‍👨‍👦 👨‍👨‍👧 👨‍👨‍👧‍👦 👨‍👨‍👦‍👦 👨‍👨‍👧‍👧 👩‍👦 👩‍👧 👩‍👧‍👦 👩‍👦‍👦 👩‍👧‍👧 👨‍👦 👨‍👧 👨‍👧‍👦 👨‍👦‍👦 👨‍👧‍👧 🤲 👐 🙌 👏 🤝 👍 👎 👊 ✊ 🤛 🤜 🤞 ✌️ 🤟 🤘 👌 👈 👉 👆 👇 ☝️ ✋ 🤚 🖐 🖖 👋 🤙 💪 🦵 🦶 🖕 ✍️ 🙏 💍 💄 💋 👄 👅 👂 👃 👣 👁 👀 🧠 🦴 🦷 🗣 👤 👥 @@ -53,7 +65,13 @@ In respect that the structure of the sufficient amount poses problems and challe 🥱 🤏 🦾 🦿 🦻 🧏 🧏‍♂️ 🧏‍♀️ 🧍 🧍‍♂️ 🧍‍♀️ 🧎 🧎‍♂️ 🧎‍♀️ 👨‍🦯 👩‍🦯 👨‍🦼 👩‍🦼 👨‍🦽 👩‍🦽 🦧 🦮 🐕‍🦺 🦥 🦦 🦨 🦩 🧄 🧅 🧇 🧆 🧈 🦪 🧃 🧉 🧊 🛕 🦽 🦼 🛺 🪂 🪐 🤿 🪀 🪁 🦺 🥻 🩱 🩲 🩳 🩰 🪕 🪔 🪓 🦯 🩸 🩹 🩺 🪑 🪒 🤎 🤍 🟠 🟡 🟢 🟣 🟤 🟥 🟧 🟨 🟩 🟦 🟪 🟫"; [Benchmark] - public TextLayout BuildEmojisTextLayout() => new TextLayout(Emojis, Typeface.Default, 12d, Brushes.Black); + public TextLayout BuildEmojisTextLayout() => MakeLayout(Emojis); + + [Benchmark] + public TextLayout[] BuildManySmallTexts() => _manySmallStrings.Select(MakeLayout).ToArray(); + + private static TextLayout MakeLayout(string str) + => new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth:120); public void Dispose() { From a78b0edd425aca64ed44eb497b52a0b7c0db4603 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 8 Jan 2023 00:17:53 -0500 Subject: [PATCH 187/213] Changes after review --- NOTICE.md | 46 +++++++++++++++---- .../Data/Core/ExpressionObserver.cs | 4 +- src/Avalonia.Base/Reactive/Operators/Sink.cs | 2 + .../Avalonia.Markup/Data/BindingBase.cs | 5 +- .../Parsers/ExpressionObserverBuilder.cs | 2 +- .../Core/ExpressionObserverTests_Lifetime.cs | 6 +-- .../Core/ExpressionObserverTests_Property.cs | 12 ++--- 7 files changed, 53 insertions(+), 24 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index bd26b65d70..7083706c3e 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -81,14 +81,14 @@ A "contributor" is any person that distributes its contribution under this licen https://github.com/wayland-project/wayland-protocols -Copyright 2008-2013 Kristian Hgsberg -Copyright 2010-2013 Intel Corporation -Copyright 2013 Rafael Antognolli -Copyright 2013 Jasper St. Pierre -Copyright 2014 Jonas dahl -Copyright 2014 Jason Ekstrand -Copyright 2014-2015 Collabora, Ltd. -Copyright 2015 Red Hat Inc. +Copyright © 2008-2013 Kristian Høgsberg +Copyright © 2010-2013 Intel Corporation +Copyright © 2013 Rafael Antognolli +Copyright © 2013 Jasper St. Pierre +Copyright © 2014 Jonas Ådahl +Copyright © 2014 Jason Ekstrand +Copyright © 2014-2015 Collabora, Ltd. +Copyright © 2015 Red Hat Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -140,7 +140,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. https://github.com/toptensoftware/RichTextKit -Copyright 2019 Topten Software. All Rights Reserved. +Copyright © 2019 Topten Software. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this product except in compliance with the License. You may obtain @@ -334,3 +334,31 @@ https://github.com/flutter/flutter //ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS //SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Reactive Extensions + +https://github.com/dotnet/reactive + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index d1d7b98562..0818b5fa62 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -104,7 +104,7 @@ namespace Avalonia.Data.Core public ExpressionObserver( Func rootGetter, ExpressionNode node, - IObservable update, + IObservable update, string? description) { Description = description; @@ -171,7 +171,7 @@ namespace Avalonia.Data.Core public static ExpressionObserver Create( Func rootGetter, Expression> expression, - IObservable update, + IObservable update, bool enableDataValidation = false, string? description = null) { diff --git a/src/Avalonia.Base/Reactive/Operators/Sink.cs b/src/Avalonia.Base/Reactive/Operators/Sink.cs index 7a5d39788e..0fef350acc 100644 --- a/src/Avalonia.Base/Reactive/Operators/Sink.cs +++ b/src/Avalonia.Base/Reactive/Operators/Sink.cs @@ -3,6 +3,8 @@ using System.Threading; namespace Avalonia.Reactive.Operators; +// Code based on https://github.com/dotnet/reactive/blob/main/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs + internal abstract class Sink : IDisposable { private IDisposable? _upstream; diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 56aa930869..90f312a249 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -252,9 +252,8 @@ namespace Avalonia.Data }).Switch(); } - private class UpdateSignal : SingleSubscriberObservableBase + private class UpdateSignal : SingleSubscriberObservableBase { - private static readonly object s_val = new(); private readonly AvaloniaObject _target; private readonly AvaloniaProperty _property; @@ -278,7 +277,7 @@ namespace Avalonia.Data { if (e.Property == _property) { - PublishNext(s_val); + PublishNext(default); } } } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index a478af570a..00f40dfcd3 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -63,7 +63,7 @@ namespace Avalonia.Markup.Parsers public static ExpressionObserver Build( Func rootGetter, string expression, - IObservable update, + IObservable update, bool enableDataValidation = false, string? description = null, Func? typeResolver = null) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs index e6864e6806..612f3ff80a 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Lifetime.cs @@ -41,7 +41,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Complete_When_Update_Observable_Completes() { - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => 1, o => o, update); var completed = false; @@ -54,7 +54,7 @@ namespace Avalonia.Base.UnitTests.Data.Core [Fact] public void Should_Complete_When_Update_Observable_Errors() { - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => 1, o => o, update); var completed = false; @@ -87,7 +87,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Should_Unsubscribe_From_Update_Observable() { var scheduler = new TestScheduler(); - var update = scheduler.CreateColdObservable(); + var update = scheduler.CreateColdObservable(); var data = new { Foo = "foo" }; var target = ExpressionObserver.Create(() => data, o => o.Foo, update); var result = new List(); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 6554be2e89..22d5645f76 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -359,14 +359,14 @@ namespace Avalonia.Base.UnitTests.Data.Core public void Empty_Expression_Should_Track_Root() { var data = new Class1 { Foo = "foo" }; - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => data.Foo, o => o, update); var result = new List(); target.Subscribe(x => result.Add(x)); data.Foo = "bar"; - update.OnNext(Unit.Default); + update.OnNext(default); Assert.Equal(new[] { "foo", "bar" }, result); @@ -533,15 +533,15 @@ namespace Avalonia.Base.UnitTests.Data.Core var first = new Class1 { Foo = "foo" }; var second = new Class1 { Foo = "bar" }; var root = first; - var update = new Subject(); + var update = new Subject(); var target = ExpressionObserver.Create(() => root, o => o.Foo, update); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); root = second; - update.OnNext(Unit.Default); + update.OnNext(default); root = null; - update.OnNext(Unit.Default); + update.OnNext(default); Assert.Equal( new object[] @@ -640,7 +640,7 @@ namespace Avalonia.Base.UnitTests.Data.Core public void RootGetter_Is_Reevaluated_On_Subscribe() { var data = "foo"; - var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject(), null); + var target = new ExpressionObserver(() => data, new EmptyExpressionNode(), new Subject(), null); var result = new List(); var sub = target.Subscribe(x => result.Add(x)); From 24626da4893f2a48ae61f5a8f0860d1d1f7a7ffb Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 8 Jan 2023 18:43:06 -0500 Subject: [PATCH 188/213] Remove Rx.props from ControlCatalog and some other projects too --- samples/ControlCatalog/Pages/MenuPage.xaml.cs | 1 - samples/ControlCatalog/Pages/PointerContactsTab.cs | 1 - .../ViewModels/ComboBoxPageViewModel.cs | 1 - .../ViewModels/ContextPageViewModel.cs | 1 - .../ViewModels/ListBoxPageViewModel.cs | 1 - .../ViewModels/MainWindowViewModel.cs | 2 +- .../ControlCatalog/ViewModels/MenuPageViewModel.cs | 2 -- .../ViewModels/NotificationViewModel.cs | 3 +-- .../ViewModels/RefreshContainerViewModel.cs | 1 - .../ViewModels/TreeViewPageViewModel.cs | 1 - samples/MiniMvvm/MiniMvvm.csproj | 4 +++- samples/MiniMvvm/PropertyChangedExtensions.cs | 14 ++++++++------ samples/MiniMvvm/ViewModelBase.cs | 1 - samples/ReactiveUIDemo/ReactiveUIDemo.csproj | 1 - .../WindowsInteropTest/WindowsInteropTest.csproj | 2 -- src/Avalonia.Base/Avalonia.Base.csproj | 2 ++ 16 files changed, 15 insertions(+), 23 deletions(-) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml.cs b/samples/ControlCatalog/Pages/MenuPage.xaml.cs index 52c122f2bc..32cfa9b22b 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/MenuPage.xaml.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Reactive; using System.Threading.Tasks; using System.Windows.Input; using Avalonia.Controls; diff --git a/samples/ControlCatalog/Pages/PointerContactsTab.cs b/samples/ControlCatalog/Pages/PointerContactsTab.cs index b6aabebf99..1751b046c0 100644 --- a/samples/ControlCatalog/Pages/PointerContactsTab.cs +++ b/samples/ControlCatalog/Pages/PointerContactsTab.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reactive.Linq; using Avalonia; using Avalonia.Controls; diff --git a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs index bbe970afd6..d3e4ea7c31 100644 --- a/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ComboBoxPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.Selection; using MiniMvvm; diff --git a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs index 4a5462a58b..f041f32b10 100644 --- a/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ContextPageViewModel.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Reactive; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; diff --git a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs index f89d9d1e20..7f32536b11 100644 --- a/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.Selection; using ControlCatalog.Pages; diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 47c6f70714..3628a9b8a7 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -1,9 +1,9 @@ -using System.Reactive; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.Notifications; using Avalonia.Dialogs; using Avalonia.Platform; +using Avalonia.Reactive; using System; using System.ComponentModel.DataAnnotations; using MiniMvvm; diff --git a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs index 16051c3c05..df62ba04cb 100644 --- a/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MenuPageViewModel.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -using System.Reactive; -using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.VisualTree; diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs index a31f164a2a..bcbcb345ef 100644 --- a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs +++ b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs @@ -1,5 +1,4 @@ -using System.Reactive; -using Avalonia.Controls.Notifications; +using Avalonia.Controls.Notifications; using MiniMvvm; namespace ControlCatalog.ViewModels diff --git a/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs b/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs index d4b43043be..aa15d7758b 100644 --- a/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs +++ b/samples/ControlCatalog/ViewModels/RefreshContainerViewModel.cs @@ -1,6 +1,5 @@ using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using System.Threading.Tasks; using Avalonia.Controls.Notifications; using ControlCatalog.Pages; diff --git a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs index 80d4844f7a..7c0855e0af 100644 --- a/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs +++ b/samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs @@ -1,7 +1,6 @@ using System; using System.Collections.ObjectModel; using System.Linq; -using System.Reactive; using Avalonia.Controls; using MiniMvvm; diff --git a/samples/MiniMvvm/MiniMvvm.csproj b/samples/MiniMvvm/MiniMvvm.csproj index 6535b2bdbd..2a9164624a 100644 --- a/samples/MiniMvvm/MiniMvvm.csproj +++ b/samples/MiniMvvm/MiniMvvm.csproj @@ -2,5 +2,7 @@ netstandard2.0 - + + + diff --git a/samples/MiniMvvm/PropertyChangedExtensions.cs b/samples/MiniMvvm/PropertyChangedExtensions.cs index f1065c7530..f51773810d 100644 --- a/samples/MiniMvvm/PropertyChangedExtensions.cs +++ b/samples/MiniMvvm/PropertyChangedExtensions.cs @@ -1,8 +1,8 @@ using System; using System.ComponentModel; using System.Linq.Expressions; -using System.Reactive.Linq; using System.Reflection; +using Avalonia.Reactive; namespace MiniMvvm { @@ -92,11 +92,13 @@ namespace MiniMvvm Expression> v3, Func cb ) where TModel : INotifyPropertyChanged => - Observable.CombineLatest( - model.WhenAnyValue(v1), - model.WhenAnyValue(v2), - model.WhenAnyValue(v3), - cb); + model.WhenAnyValue(v1) + .CombineLatest( + model.WhenAnyValue(v2), + (l, r) => (l, r)) + .CombineLatest( + model.WhenAnyValue(v3), + (t, r) => cb(t.l, t.r, r)); public static IObservable> WhenAnyValue(this TModel model, Expression> v1, diff --git a/samples/MiniMvvm/ViewModelBase.cs b/samples/MiniMvvm/ViewModelBase.cs index 7256b05cef..8bc398607f 100644 --- a/samples/MiniMvvm/ViewModelBase.cs +++ b/samples/MiniMvvm/ViewModelBase.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.ComponentModel; -using System.Reactive.Joins; using System.Runtime.CompilerServices; namespace MiniMvvm diff --git a/samples/ReactiveUIDemo/ReactiveUIDemo.csproj b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj index 94ca4ee809..9650068434 100644 --- a/samples/ReactiveUIDemo/ReactiveUIDemo.csproj +++ b/samples/ReactiveUIDemo/ReactiveUIDemo.csproj @@ -23,6 +23,5 @@ - diff --git a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj index 223a53d8c5..1643ca3ee2 100644 --- a/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj +++ b/samples/interop/WindowsInteropTest/WindowsInteropTest.csproj @@ -15,6 +15,4 @@ ControlCatalog - - diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 1e02d6a8f3..cd122a8b67 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -58,6 +58,8 @@ + + From a5ee7ebde83a06ccfcd229ffd26c05ee3e18934f Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 9 Jan 2023 13:59:42 +0100 Subject: [PATCH 189/213] Resolve conflicts --- .../TextFormatting/CharacterBufferRange.cs | 11 +- .../Media/TextFormatting/TextFormatterImpl.cs | 150 +++++++++--------- .../TextLeadingPrefixCharacterEllipsis.cs | 2 +- .../Media/TextFormatting/TextLineImpl.cs | 4 +- .../Media/TextFormatting/TextRun.cs | 2 +- 5 files changed, 87 insertions(+), 82 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs index f5d39e4371..499026e8b3 100644 --- a/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs +++ b/src/Avalonia.Base/Media/TextFormatting/CharacterBufferRange.cs @@ -140,7 +140,7 @@ namespace Avalonia.Media.TextFormatting throw new ArgumentOutOfRangeException(nameof(index)); } #endif - return CharacterBufferReference.CharacterBuffer.Span[CharacterBufferReference.OffsetToFirstChar + index]; + return CharacterBuffer.Span[CharacterBufferReference.OffsetToFirstChar + index]; } } @@ -157,8 +157,7 @@ namespace Avalonia.Media.TextFormatting /// /// Gets a span from the character buffer range /// - public ReadOnlySpan Span => - CharacterBufferReference.CharacterBuffer.Span.Slice(CharacterBufferReference.OffsetToFirstChar, Length); + public ReadOnlySpan Span => CharacterBuffer.Span.Slice(OffsetToFirstChar, Length); /// /// Gets the character memory buffer @@ -174,7 +173,7 @@ namespace Avalonia.Media.TextFormatting /// /// Indicate whether the character buffer range is empty /// - internal bool IsEmpty => CharacterBufferReference.CharacterBuffer.Length == 0 || Length <= 0; + internal bool IsEmpty => CharacterBuffer.Length == 0 || Length <= 0; internal CharacterBufferRange Take(int length) { @@ -208,9 +207,7 @@ namespace Avalonia.Media.TextFormatting return new CharacterBufferRange(new CharacterBufferReference(), 0); } - var characterBufferReference = new CharacterBufferReference( - CharacterBufferReference.CharacterBuffer, - CharacterBufferReference.OffsetToFirstChar + length); + var characterBufferReference = new CharacterBufferReference(CharacterBuffer, OffsetToFirstChar + length); return new CharacterBufferRange(characterBufferReference, Length - length); } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 544800ecea..989bf7749d 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -130,7 +130,7 @@ namespace Avalonia.Media.TextFormatting first.Add(split.First); second.Add(split.Second!); - } + } for (var j = 1; j < secondCount; j++) { @@ -237,7 +237,7 @@ namespace Avalonia.Media.TextFormatting var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface, currentRun.Properties.FontRenderingEmSize, - shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, + shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); shapedRuns.AddRange(ShapeTogether(groupedRuns, characterBufferReference, length, shaperOptions)); @@ -478,7 +478,7 @@ namespace Avalonia.Media.TextFormatting { case ShapedTextRun shapedTextCharacters: { - if(shapedTextCharacters.ShapedBuffer.Length > 0) + if (shapedTextCharacters.ShapedBuffer.Length > 0) { var firstCluster = shapedTextCharacters.ShapedBuffer.GlyphInfos[0].GlyphCluster; var lastCluster = firstCluster; @@ -499,7 +499,7 @@ namespace Avalonia.Media.TextFormatting } measuredLength += currentRun.Length; - } + } break; } @@ -525,7 +525,7 @@ namespace Avalonia.Media.TextFormatting } } - found: + found: return measuredLength != 0; } @@ -565,9 +565,9 @@ namespace Avalonia.Media.TextFormatting double paragraphWidth, TextParagraphProperties paragraphProperties, FlowDirection resolvedFlowDirection, TextLineBreak? currentLineBreak) { - if(textRuns.Count == 0) + if (textRuns.Count == 0) { - return CreateEmptyTextLine(firstTextSourceIndex,paragraphWidth, paragraphProperties); + return CreateEmptyTextLine(firstTextSourceIndex, paragraphWidth, paragraphProperties); } if (!TryMeasureLength(textRuns, paragraphWidth, out var measuredLength)) @@ -583,46 +583,24 @@ namespace Avalonia.Media.TextFormatting for (var index = 0; index < textRuns.Count; index++) { - var currentRun = textRuns[index]; - - var runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); - - var lineBreaker = new LineBreakEnumerator(runText); - var breakFound = false; - while (lineBreaker.MoveNext()) - { - if (lineBreaker.Current.Required && - currentLength + lineBreaker.Current.PositionMeasure <= measuredLength) - { - //Explicit break found - breakFound = true; - - currentPosition = currentLength + lineBreaker.Current.PositionWrap; - - break; - } + var currentRun = textRuns[index]; - if (currentLength + lineBreaker.Current.PositionMeasure > measuredLength) - { - if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) + switch (currentRun) + { + case ShapedTextRun: { - if (lastWrapPosition > 0) - { - currentPosition = lastWrapPosition; + var runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); - breakFound = true; + var lineBreaker = new LineBreakEnumerator(runText); - break; - } - - //Find next possible wrap position (overflow) - if (index < textRuns.Count - 1) + while (lineBreaker.MoveNext()) { - if (lineBreaker.Current.PositionWrap != currentRun.Length) + if (lineBreaker.Current.Required && + currentLength + lineBreaker.Current.PositionMeasure <= measuredLength) { - //We already found the next possible wrap position. + //Explicit break found breakFound = true; currentPosition = currentLength + lineBreaker.Current.PositionWrap; @@ -630,51 +608,81 @@ namespace Avalonia.Media.TextFormatting break; } - while (lineBreaker.MoveNext() && index < textRuns.Count) + if (currentLength + lineBreaker.Current.PositionMeasure > measuredLength) { - currentPosition += lineBreaker.Current.PositionWrap; - - if (lineBreaker.Current.PositionWrap != currentRun.Length) + if (paragraphProperties.TextWrapping == TextWrapping.WrapWithOverflow) { - break; - } + if (lastWrapPosition > 0) + { + currentPosition = lastWrapPosition; - index++; + breakFound = true; + + break; + } + + //Find next possible wrap position (overflow) + if (index < textRuns.Count - 1) + { + if (lineBreaker.Current.PositionWrap != currentRun.Length) + { + //We already found the next possible wrap position. + breakFound = true; + + currentPosition = currentLength + lineBreaker.Current.PositionWrap; + + break; + } + + while (lineBreaker.MoveNext() && index < textRuns.Count) + { + currentPosition += lineBreaker.Current.PositionWrap; + + if (lineBreaker.Current.PositionWrap != currentRun.Length) + { + break; + } + + index++; + + if (index >= textRuns.Count) + { + break; + } + + currentRun = textRuns[index]; + + runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); + + lineBreaker = new LineBreakEnumerator(runText); + } + } + else + { + currentPosition = currentLength + lineBreaker.Current.PositionWrap; + } + + breakFound = true; - if (index >= textRuns.Count) - { break; } - currentRun = textRuns[index]; + //We overflowed so we use the last available wrap position. + currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition; - runText = new CharacterBufferRange(currentRun.CharacterBufferReference, currentRun.Length); + breakFound = true; - lineBreaker = new LineBreakEnumerator(runText); + break; } - } - else - { - currentPosition = currentLength + lineBreaker.Current.PositionWrap; - } - breakFound = true; + if (lineBreaker.Current.PositionMeasure != lineBreaker.Current.PositionWrap) + { + lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap; + } + } break; } - - //We overflowed so we use the last available wrap position. - currentPosition = lastWrapPosition == 0 ? measuredLength : lastWrapPosition; - - breakFound = true; - - break; - } - - if (lineBreaker.Current.PositionMeasure != lineBreaker.Current.PositionWrap) - { - lastWrapPosition = currentLength + lineBreaker.Current.PositionWrap; - } } if (!breakFound) @@ -694,7 +702,7 @@ namespace Avalonia.Media.TextFormatting var remainingCharacters = splitResult.Second; var lineBreak = remainingCharacters?.Count > 0 ? - new TextLineBreak(currentLineBreak?.TextEndOfLine, resolvedFlowDirection, remainingCharacters) : + new TextLineBreak(null, resolvedFlowDirection, remainingCharacters) : null; if (lineBreak is null && currentLineBreak?.TextEndOfLine != null) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs index 42a84ec137..e30a0fe9f4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLeadingPrefixCharacterEllipsis.cs @@ -123,7 +123,7 @@ namespace Avalonia.Media.TextFormatting switch (run) { - case ShapedTextCharacters endShapedRun: + case ShapedTextRun endShapedRun: { if (endShapedRun.TryMeasureCharactersBackwards(availableSuffixWidth, out var suffixCount, out var suffixWidth)) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 5a71bde522..a1f93bcd07 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -230,12 +230,12 @@ namespace Avalonia.Media.TextFormatting currentRun = _textRuns[j]; - if(currentRun is not ShapedTextCharacters) + if(currentRun is not ShapedTextRun) { continue; } - shapedRun = (ShapedTextCharacters)currentRun; + shapedRun = (ShapedTextRun)currentRun; if (currentDistance + shapedRun.Size.Width <= distance) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs index 21f85b898f..56232ec9c8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextRun.cs @@ -44,7 +44,7 @@ namespace Avalonia.Media.TextFormatting fixed (char* charsPtr = characterBuffer.Span) { - return new string(charsPtr, _textRun.CharacterBufferReference.OffsetToFirstChar, _textRun.Length); + return new string(charsPtr, 0, _textRun.Length); } } } From 182e469066a3da780bd8c50777127fb0fad8ebf8 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 9 Jan 2023 23:51:18 +0800 Subject: [PATCH 190/213] feat: add UT for GeometryTypeConverter. --- src/Avalonia.Base/Media/Geometry.cs | 4 +- .../Converters/GeometryTypeConverterTests.cs | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Converters/GeometryTypeConverterTests.cs diff --git a/src/Avalonia.Base/Media/Geometry.cs b/src/Avalonia.Base/Media/Geometry.cs index e2b345df1a..e62f819b74 100644 --- a/src/Avalonia.Base/Media/Geometry.cs +++ b/src/Avalonia.Base/Media/Geometry.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media /// /// Defines a geometric shape. /// - [TypeConverter(typeof(GeometryConverter))] + [TypeConverter(typeof(GeometryTypeConverter))] public abstract class Geometry : AvaloniaObject { /// @@ -203,7 +203,7 @@ namespace Avalonia.Media } } - public class GeometryConverter : TypeConverter + public class GeometryTypeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/GeometryTypeConverterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/GeometryTypeConverterTests.cs new file mode 100644 index 0000000000..329a14afa6 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/GeometryTypeConverterTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Shapes; +using Avalonia.Media; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Converters +{ + public class GeometryTypeConverterTests: XamlTestBase + { + public class StringDataViewModel + { + public string PathData { get; set; } + } + + public class IntDataViewModel + { + public int PathData { get; set; } + } + + + [Theory] + [MemberData(nameof(Get_GeometryTypeConverter_Data))] + public void GeometryTypeConverter_Value_Work(object vm, bool nullData) + { + using(UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var path = window.FindControl("path"); + window.DataContext = vm; + Assert.Equal(nullData, path.Data is null); + } + } + + public static IEnumerable Get_GeometryTypeConverter_Data() + { + yield return new object[] { new StringDataViewModel { }, true }; + yield return new object[] { new StringDataViewModel { PathData = "M406.39,333.45l205.93,0" }, false }; + yield return new object[] { new IntDataViewModel { }, true }; + yield return new object[] { new IntDataViewModel { PathData = 100 }, true }; + } + } +} From 9d075fe2a9ead45840670b2f13b322416ed626c1 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Tue, 10 Jan 2023 07:49:29 +1100 Subject: [PATCH 191/213] Update ControlTemplateTests.cs --- .../Xaml/ControlTemplateTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs index 6c29707dcd..396a03645c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ControlTemplateTests.cs @@ -27,11 +27,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml From ba90d9ab98422d40d99a4f1a6dd48c417761c150 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 10 Jan 2023 01:03:51 +0100 Subject: [PATCH 192/213] Clean/Rebuild removes old resources cache from obj folder --- packages/Avalonia/AvaloniaBuildTasks.targets | 25 +++++++++++++---- .../CompileAvaloniaXamlTask.cs | 28 ++++++++++++++++--- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index ca2d4b66ed..33f22f4d02 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -48,8 +48,12 @@ + + <_AvaloniaResourcesInputsCacheFilePath>$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache + + - + @@ -57,7 +61,11 @@ - + + + + + - + + + + @@ -103,6 +114,9 @@ File="$(AvaloniaXamlReferencesTemporaryFilePath)" Lines="@(ReferencePathWithRefAssemblies)" Overwrite="true" /> + + + + DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"> + + diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs index fd07e0a143..3bd1ce5ce7 100644 --- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs +++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Framework; @@ -10,9 +11,10 @@ namespace Avalonia.Build.Tasks public bool Execute() { Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance); + var writtenFilePaths = new List(); - OutputPath = OutputPath ?? AssemblyFile; - RefOutputPath = RefOutputPath ?? RefAssemblyFile; + OutputPath ??= AssemblyFile; + RefOutputPath ??= RefAssemblyFile; var outputPdb = GetPdbPath(OutputPath); var input = AssemblyFile; var refInput = RefOutputPath; @@ -21,8 +23,9 @@ namespace Avalonia.Build.Tasks if (OriginalCopyPath != null) { var originalCopyPathRef = Path.ChangeExtension(OriginalCopyPath, ".ref.dll"); - + File.Copy(AssemblyFile, OriginalCopyPath, true); + writtenFilePaths.Add(OriginalCopyPath); input = OriginalCopyPath; File.Delete(AssemblyFile); @@ -30,6 +33,7 @@ namespace Avalonia.Build.Tasks { var copyPdb = GetPdbPath(OriginalCopyPath); File.Copy(inputPdb, copyPdb, true); + writtenFilePaths.Add(copyPdb); File.Delete(inputPdb); inputPdb = copyPdb; } @@ -39,6 +43,7 @@ namespace Avalonia.Build.Tasks // We also copy ref assembly just for case if needed later for testing. // But do not remove the original one, as MSBuild actually complains about it with multi-thread compiling. File.Copy(RefAssemblyFile, originalCopyPathRef, true); + writtenFilePaths.Add(originalCopyPathRef); refInput = originalCopyPathRef; } } @@ -53,13 +58,25 @@ namespace Avalonia.Build.Tasks ProjectDirectory, VerifyIl, DefaultCompileBindings, outputImportance, (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch); if (!res.Success) + { + WrittenFilePaths = writtenFilePaths.ToArray(); return false; + } + if (!res.WrittenFile) { File.Copy(input, OutputPath, true); - if(File.Exists(inputPdb)) + if (File.Exists(inputPdb)) File.Copy(inputPdb, outputPdb, true); } + else if (!string.IsNullOrWhiteSpace(RefOutputPath) && File.Exists(RefOutputPath)) + writtenFilePaths.Add(RefOutputPath); + + writtenFilePaths.Add(OutputPath); + if (File.Exists(outputPdb)) + writtenFilePaths.Add(outputPdb); + + WrittenFilePaths = writtenFilePaths.ToArray(); return true; } @@ -103,5 +120,8 @@ namespace Avalonia.Build.Tasks public ITaskHost HostObject { get; set; } public bool DebuggerLaunch { get; set; } + + [Output] + public string[] WrittenFilePaths { get; private set; } = Array.Empty(); } } From b8a4de969dd6024f75d5d1a09fa51e20a02faaff Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 10 Jan 2023 13:55:34 +0100 Subject: [PATCH 193/213] Made resources index binary Effectively removes System.Xml.Linq dependency --- .../Platform/Internal/AssemblyDescriptor.cs | 2 +- .../Utilities/AvaloniaResourcesIndex.cs | 149 ++++++++++-------- .../Avalonia.Build.Tasks.csproj | 3 + .../GenerateAvaloniaResourcesTask.cs | 26 +-- .../XamlCompilerTaskExecutor.Helpers.cs | 21 ++- 5 files changed, 103 insertions(+), 98 deletions(-) diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs index df2a26ddd3..467cd530fc 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs +++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs @@ -32,7 +32,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor Resources.Remove(Constants.AvaloniaResourceName); var indexLength = new BinaryReader(resources).ReadInt32(); - var index = AvaloniaResourcesIndexReaderWriter.Read(new SlicedStream(resources, 4, indexLength)); + var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength)); var baseOffset = indexLength + 4; AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor) new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); diff --git a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs index 5235e23e08..3c7e82f080 100644 --- a/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs +++ b/src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs @@ -1,105 +1,116 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.Serialization; -using System.Xml.Linq; -using System.Linq; -using System.Diagnostics.CodeAnalysis; +using System.Text; namespace Avalonia.Utilities { - #if !BUILDTASK +#if !BUILDTASK public - #endif +#endif static class AvaloniaResourcesIndexReaderWriter { - private const int LastKnownVersion = 1; - public static List Read(Stream stream) + private const int XmlLegacyVersion = 1; + private const int BinaryCurrentVersion = 2; + + public static List ReadIndex(Stream stream) { - var ver = new BinaryReader(stream).ReadInt32(); - if (ver > LastKnownVersion) - throw new Exception("Resources index format version is not known"); - - var assetDoc = XDocument.Load(stream); - XNamespace assetNs = assetDoc.Root!.Attribute("xmlns")!.Value; - List entries = - (from entry in assetDoc.Root.Element(assetNs + "Entries")!.Elements(assetNs + "AvaloniaResourcesIndexEntry") - select new AvaloniaResourcesIndexEntry - { - Path = entry.Element(assetNs + "Path")!.Value, - Offset = int.Parse(entry.Element(assetNs + "Offset")!.Value), - Size = int.Parse(entry.Element(assetNs + "Size")!.Value) - }).ToList(); + using var reader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true); - return entries; + var version = reader.ReadInt32(); + return version switch + { + XmlLegacyVersion => ReadXmlIndex(), + BinaryCurrentVersion => ReadBinaryIndex(reader), + _ => throw new Exception($"Unknown resources index format version {version}") + }; } - [RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")] - public static void Write(Stream stream, List entries) + private static List ReadXmlIndex() + => throw new NotSupportedException("Found legacy resources index format: please recompile your XAML files"); + + private static List ReadBinaryIndex(BinaryReader reader) { - new BinaryWriter(stream).Write(LastKnownVersion); - new DataContractSerializer(typeof(AvaloniaResourcesIndex)).WriteObject(stream, - new AvaloniaResourcesIndex() - { - Entries = entries + var entryCount = reader.ReadInt32(); + var entries = new List(entryCount); + + for (var i = 0; i < entryCount; ++i) + { + entries.Add(new AvaloniaResourcesIndexEntry { + Path = reader.ReadString(), + Offset = reader.ReadInt32(), + Size = reader.ReadInt32() }); + } + + return entries; } - [RequiresUnreferencedCode("AvaloniaResources uses Data Contract Serialization, which might require unreferenced code")] - public static byte[] Create(Dictionary data) + public static void WriteIndex(Stream output, List entries) { - var sources = data.ToList(); - var offsets = new Dictionary(); - var coffset = 0; - foreach (var s in sources) + using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true); + + WriteIndex(writer, entries); + } + + private static void WriteIndex(BinaryWriter writer, List entries) + { + writer.Write(BinaryCurrentVersion); + writer.Write(entries.Count); + + foreach (var entry in entries) { - offsets[s.Key] = coffset; - coffset += s.Value.Length; + writer.Write(entry.Path ?? string.Empty); + writer.Write(entry.Offset); + writer.Write(entry.Size); } - var index = sources.Select(s => new AvaloniaResourcesIndexEntry - { - Path = s.Key, - Size = s.Value.Length, - Offset = offsets[s.Key] - }).ToList(); - var output = new MemoryStream(); - var ms = new MemoryStream(); - AvaloniaResourcesIndexReaderWriter.Write(ms, index); - new BinaryWriter(output).Write((int)ms.Length); - ms.Position = 0; - ms.CopyTo(output); - foreach (var s in sources) + } + + public static void WriteResources(Stream output, List<(string Path, int Size, Func Open)> resources) + { + var entries = new List(resources.Count); + var offset = 0; + + foreach (var resource in resources) { - output.Write(s.Value,0,s.Value.Length); + entries.Add(new AvaloniaResourcesIndexEntry + { + Path = resource.Path, + Offset = offset, + Size = resource.Size + }); + offset += resource.Size; } - return output.ToArray(); - } - } + using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true); + writer.Write(0); // index size placeholder, overwritten below - [DataContract] -#if !BUILDTASK - public -#endif - class AvaloniaResourcesIndex - { - [DataMember] - public List Entries { get; set; } = new List(); + var posBeforeEntries = output.Position; + WriteIndex(writer, entries); + + var posAfterEntries = output.Position; + var indexSize = (int) (posAfterEntries - posBeforeEntries); + output.Position = 0L; + writer.Write(indexSize); + output.Position = posAfterEntries; + + foreach (var resource in resources) + { + using var resourceStream = resource.Open(); + resourceStream.CopyTo(output); + } + } } - [DataContract] #if !BUILDTASK public #endif class AvaloniaResourcesIndexEntry { - [DataMember] public string? Path { get; set; } - - [DataMember] + public int Offset { get; set; } - - [DataMember] + public int Size { get; set; } } } diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 35b22f4f23..e44b7290af 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -18,6 +18,9 @@ Shared/AvaloniaResourcesIndex.cs + + Shared/Constants.cs + Shared/AvaloniaResourceXamlInfo.cs diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index 65681a2d28..264a86b014 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -77,29 +77,9 @@ namespace Avalonia.Build.Tasks private void Pack(Stream output, List sources) { - var offsets = new Dictionary(); - var coffset = 0; - foreach (var s in sources) - { - offsets[s] = coffset; - coffset += s.Size; - } - var index = sources.Select(s => new AvaloniaResourcesIndexEntry - { - Path = s.Path, - Size = s.Size, - Offset = offsets[s] - }).ToList(); - var ms = new MemoryStream(); - AvaloniaResourcesIndexReaderWriter.Write(ms, index); - new BinaryWriter(output).Write((int)ms.Length); - ms.Position = 0; - ms.CopyTo(output); - foreach (var s in sources) - { - using (var input = s.Open()) - input.CopyTo(output); - } + AvaloniaResourcesIndexReaderWriter.WriteResources( + output, + sources.Select(source => (source.Path, source.Size, (Func) source.Open)).ToList()); } private bool PreProcessXamlFiles(List sources) diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs index f83e07bd74..079ea91db1 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Avalonia.Platform.Internal; using Avalonia.Utilities; using Mono.Cecil; using Mono.Cecil.Cil; @@ -34,13 +36,13 @@ namespace Avalonia.Build.Tasks { _asm = asm; _embedded = ((EmbeddedResource)asm.MainModule.Resources.FirstOrDefault(r => - r.ResourceType == ResourceType.Embedded && r.Name == "!AvaloniaResources")); + r.ResourceType == ResourceType.Embedded && r.Name == Constants.AvaloniaResourceName)); if (_embedded == null) return; using (var stream = _embedded.GetResourceStream()) { var br = new BinaryReader(stream); - var index = AvaloniaResourcesIndexReaderWriter.Read(new MemoryStream(br.ReadBytes(br.ReadInt32()))); + var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new MemoryStream(br.ReadBytes(br.ReadInt32()))); var baseOffset = stream.Position; foreach (var e in index) { @@ -61,9 +63,18 @@ namespace Avalonia.Build.Tasks if (_resources.Count == 0) return; - _embedded = new EmbeddedResource("!AvaloniaResources", ManifestResourceAttributes.Public, - AvaloniaResourcesIndexReaderWriter.Create(_resources.ToDictionary(x => x.Key, - x => x.Value.FileContents))); + var output = new MemoryStream(); + + AvaloniaResourcesIndexReaderWriter.WriteResources( + output, + _resources.Select(x => ( + Path: x.Key, + Size: x.Value.FileContents.Length, + Open: (Func) (() => new MemoryStream(x.Value.FileContents)) + )).ToList()); + + output.Position = 0L; + _embedded = new EmbeddedResource(Constants.AvaloniaResourceName, ManifestResourceAttributes.Public, output); _asm.MainModule.Resources.Add(_embedded); } From ae8e6cc12045aa7c4e9fe449d66eebacdd356537 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 02:18:20 +0300 Subject: [PATCH 194/213] [Text] [Optimization] [Alloc] Bidi Pooling --- .../Media/TextFormatting/TextFormatterImpl.cs | 4 ++-- .../TextFormatting/Unicode/BiDiAlgorithm.cs | 10 +++++++++- .../Media/TextFormatting/Unicode/BiDiData.cs | 12 +++++++++++- src/Avalonia.Base/Utilities/ArrayBuilder.cs | 18 ++++++++++++++---- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index ef2abdfea0..c5ded72a43 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -159,7 +159,7 @@ namespace Avalonia.Media.TextFormatting { var flowDirection = paragraphProperties.FlowDirection; var drawableTextRuns = new List(); - var biDiData = new BidiData((sbyte)flowDirection); + using var biDiData = new BidiData((sbyte)flowDirection); foreach (var textRun in textRuns) { @@ -177,7 +177,7 @@ namespace Avalonia.Media.TextFormatting } } - var biDi = new BidiAlgorithm(); + using var biDi = new BidiAlgorithm(); biDi.Process(biDiData); diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 17ec9b1df2..3372d2be51 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -27,7 +27,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// as much as possible. /// /// - internal sealed class BidiAlgorithm + internal sealed class BidiAlgorithm : IDisposable { /// /// The original BiDiClass classes as provided by the caller @@ -1714,5 +1714,13 @@ namespace Avalonia.Media.TextFormatting.Unicode public BidiClass Eos { get; } } + + public void Dispose() + { + _workingClassesBuffer.Dispose(); + _resolvedLevelsBuffer.Dispose(); + _x9Map.Dispose(); + _isolatedRunMapping.Dispose(); + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs index 0c51b0898d..7e5b3a6620 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Represents a unicode string and all associated attributes /// for each character required for the bidirectional Unicode algorithm /// - internal class BidiData + internal class BidiData : IDisposable { private ArrayBuilder _classes; private ArrayBuilder _pairedBracketTypes; @@ -181,5 +181,15 @@ namespace Avalonia.Media.TextFormatting.Unicode return _tempLevelBuffer.Add(length, false); } + + public void Dispose() + { + _classes.Dispose(); + _pairedBracketTypes.Dispose(); + _pairedBracketValues.Dispose(); + _savedClasses.Dispose(); + _savedPairedBracketTypes.Dispose(); + _tempLevelBuffer.Dispose(); + } } } diff --git a/src/Avalonia.Base/Utilities/ArrayBuilder.cs b/src/Avalonia.Base/Utilities/ArrayBuilder.cs index 60bf2c7586..d63eaddb7d 100644 --- a/src/Avalonia.Base/Utilities/ArrayBuilder.cs +++ b/src/Avalonia.Base/Utilities/ArrayBuilder.cs @@ -3,6 +3,7 @@ // Ported from: https://github.com/SixLabors/Fonts/ using System; +using System.Buffers; using System.Runtime.CompilerServices; namespace Avalonia.Utilities @@ -11,7 +12,7 @@ namespace Avalonia.Utilities /// A helper type for avoiding allocations while building arrays. /// /// The type of item contained in the array. - internal struct ArrayBuilder + internal struct ArrayBuilder : IDisposable where T : struct { private const int DefaultCapacity = 4; @@ -135,7 +136,7 @@ namespace Avalonia.Utilities } // Same expansion algorithm as List. - var newCapacity = length == 0 ? DefaultCapacity : (uint)length * 2u; + var newCapacity = length == 0 ? DefaultCapacity : length * 2; if (newCapacity > MaxCoreClrArrayLength) { @@ -144,10 +145,11 @@ namespace Avalonia.Utilities if (newCapacity < min) { - newCapacity = (uint)min; + newCapacity = min; } - var array = new T[newCapacity]; + Dispose(); + var array = ArrayPool.Shared.Rent(newCapacity); if (_size > 0) { @@ -180,5 +182,13 @@ namespace Avalonia.Utilities /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArraySlice AsSlice(int start, int length) => new ArraySlice(_data!, start, length); + + public void Dispose() + { + if (_data != null) + { + ArrayPool.Shared.Return(_data); + } + } } } From 39374451ddbc733d818255c8a06b2650796b91d3 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 02:54:04 +0300 Subject: [PATCH 195/213] [Text] Add pooling to shapedBuffer. works only with dispose. --- src/Avalonia.Base/Media/GlyphRun.cs | 2 +- .../Media/TextFormatting/ShapedBuffer.cs | 29 ++++++++++++++++--- .../Media/TextFormatting/ShapedTextRun.cs | 8 ++++- .../Media/TextFormatting/TextLayout.cs | 10 ++++++- .../Media/TextFormatting/TextLine.cs | 4 ++- .../Media/TextFormatting/TextLineImpl.cs | 9 ++++++ src/Avalonia.Base/Utilities/ArraySlice.cs | 9 ++++++ .../Text/HugeTextLayout.cs | 8 +++-- 8 files changed, 69 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index af9e458a28..811479fde8 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -918,7 +918,7 @@ namespace Avalonia.Media _glyphRunImpl = platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets); } - void IDisposable.Dispose() + public void Dispose() { _glyphRunImpl?.Dispose(); } diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 902b897240..5cf153d199 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -1,17 +1,20 @@ using System; +using System.Buffers; using System.Collections.Generic; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting { - public sealed class ShapedBuffer : IList + public sealed class ShapedBuffer : IList, IDisposable { private static readonly IComparer s_clusterComparer = new CompareClusters(); + private bool _rented; public ShapedBuffer(CharacterBufferRange characterBufferRange, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : - this(characterBufferRange, new GlyphInfo[bufferLength], glyphTypeface, fontRenderingEmSize, bidiLevel) + this(characterBufferRange, ArrayPool.Shared.Rent(bufferLength), glyphTypeface, fontRenderingEmSize, bidiLevel) { - + _rented = true; + Length = bufferLength; } internal ShapedBuffer(CharacterBufferRange characterBufferRange, ArraySlice glyphInfos, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) @@ -21,11 +24,12 @@ namespace Avalonia.Media.TextFormatting GlyphTypeface = glyphTypeface; FontRenderingEmSize = fontRenderingEmSize; BidiLevel = bidiLevel; + Length = GlyphInfos.Length; } internal ArraySlice GlyphInfos { get; } - public int Length => GlyphInfos.Length; + public int Length { get; } public IGlyphTypeface GlyphTypeface { get; } @@ -260,6 +264,23 @@ namespace Avalonia.Media.TextFormatting System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); } + + public void Dispose() + { + GC.SuppressFinalize(this); + if (_rented) + { + GlyphInfos.ReturnRent(); + } + } + + ~ShapedBuffer() + { + if (_rented) + { + GlyphInfos.ReturnRent(); + } + } } public readonly record struct GlyphInfo diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index 3149bc2cda..665723b284 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media.TextFormatting /// /// A text run that holds shaped characters. /// - public sealed class ShapedTextRun : DrawableTextRun + public sealed class ShapedTextRun : DrawableTextRun, IDisposable { private GlyphRun? _glyphRun; @@ -199,5 +199,11 @@ namespace Avalonia.Media.TextFormatting ShapedBuffer.GlyphClusters, BidiLevel); } + + public void Dispose() + { + _glyphRun?.Dispose(); + ShapedBuffer.Dispose(); + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f803001481..969b960c6b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting /// /// Represents a multi line text layout. /// - public class TextLayout + public class TextLayout : IDisposable { private readonly ITextSource _textSource; private readonly TextParagraphProperties _paragraphProperties; @@ -561,5 +561,13 @@ namespace Avalonia.Media.TextFormatting return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties)); } + + public void Dispose() + { + foreach (var line in TextLines) + { + line.Dispose(); + } + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs index 61b24dc8c5..3cb26882dc 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs @@ -6,7 +6,7 @@ namespace Avalonia.Media.TextFormatting /// /// Represents a line of text that is used for text rendering. /// - public abstract class TextLine + public abstract class TextLine : IDisposable { /// /// Gets the text runs that are contained within a line. @@ -207,5 +207,7 @@ namespace Avalonia.Media.TextFormatting /// number of characters of the specified range /// an array of bounding rectangles. public abstract IReadOnlyList GetTextBounds(int firstTextSourceCharacterIndex, int textLength); + + public abstract void Dispose(); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 5fb1171221..93ee139763 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting @@ -934,6 +935,14 @@ namespace Avalonia.Media.TextFormatting return GetTextBoundsRightToLeft(firstTextSourceIndex, textLength); } + public override void Dispose() + { + foreach (var run in _textRuns.OfType()) + { + run.Dispose(); + } + } + public TextLineImpl FinalizeLine() { _textLineMetrics = CreateLineMetrics(); diff --git a/src/Avalonia.Base/Utilities/ArraySlice.cs b/src/Avalonia.Base/Utilities/ArraySlice.cs index 39c0cd5556..b70088a907 100644 --- a/src/Avalonia.Base/Utilities/ArraySlice.cs +++ b/src/Avalonia.Base/Utilities/ArraySlice.cs @@ -3,6 +3,7 @@ // Ported from: https://github.com/SixLabors/Fonts/ using System; +using System.Buffers; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; @@ -185,5 +186,13 @@ namespace Avalonia.Utilities /// int IReadOnlyCollection.Count => Length; + + public void ReturnRent() + { + if (_data != null) + { + ArrayPool.Shared.Return(_data); + } + } } } diff --git a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs index e696b976ad..20fa268a85 100644 --- a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs +++ b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs @@ -70,8 +70,12 @@ In respect that the structure of the sufficient amount poses problems and challe [Benchmark] public TextLayout[] BuildManySmallTexts() => _manySmallStrings.Select(MakeLayout).ToArray(); - private static TextLayout MakeLayout(string str) - => new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth:120); + private static TextLayout MakeLayout(string str) + { + var layout = new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth: 120); + layout.Dispose(); + return layout; + } public void Dispose() { From f8c2288f4a738900be1b75c6c507c403f153e056 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 03:04:34 +0300 Subject: [PATCH 196/213] [Text] [Bidi] fix EnsureCapacity return --- src/Avalonia.Base/Utilities/ArrayBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Utilities/ArrayBuilder.cs b/src/Avalonia.Base/Utilities/ArrayBuilder.cs index d63eaddb7d..e6b67bd383 100644 --- a/src/Avalonia.Base/Utilities/ArrayBuilder.cs +++ b/src/Avalonia.Base/Utilities/ArrayBuilder.cs @@ -147,13 +147,13 @@ namespace Avalonia.Utilities { newCapacity = min; } - - Dispose(); + var array = ArrayPool.Shared.Rent(newCapacity); if (_size > 0) { Array.Copy(_data!, array, _size); + ArrayPool.Shared.Return(_data!); } _data = array; From dc25b802a758abd8cd6c8329224c6cb9ada45be5 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 03:30:06 +0300 Subject: [PATCH 197/213] [Text] add pooled list and pooled stack usage --- .../TextFormatting/Unicode/BiDiAlgorithm.cs | 22 ++++++++++++------- .../Media/TextFormatting/Unicode/BiDiData.cs | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 3372d2be51..0bea4def28 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; +using Avalonia.Collections.Pooled; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting.Unicode @@ -27,7 +28,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// as much as possible. /// /// - internal sealed class BidiAlgorithm : IDisposable + internal struct BidiAlgorithm : IDisposable { /// /// The original BiDiClass classes as provided by the caller @@ -97,7 +98,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The status stack used during resolution of explicit /// embedding and isolating runs /// - private readonly Stack _statusStack = new Stack(); + private readonly PooledStack _statusStack = new PooledStack(); /// /// Mapping used to virtually remove characters for rule X9 @@ -107,7 +108,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// Re-usable list of level runs /// - private readonly List _levelRuns = new List(); + private readonly PooledList _levelRuns = new PooledList(); /// /// Mapping for the current isolating sequence, built @@ -118,7 +119,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// A stack of pending isolate openings used by FindIsolatePairs() /// - private readonly Stack _pendingIsolateOpenings = new Stack(); + private readonly PooledStack _pendingIsolateOpenings = new PooledStack(); /// /// The level of the isolating run currently being processed @@ -174,17 +175,17 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Reusable list of pending opening brackets used by the /// LocatePairedBrackets method /// - private readonly List _pendingOpeningBrackets = new List(); + private readonly PooledList _pendingOpeningBrackets = new PooledList(); /// /// Resolved list of paired brackets /// - private readonly List _pairedBrackets = new List(); + private readonly PooledList _pairedBrackets = new PooledList(); /// /// Initializes a new instance of the class. /// - internal BidiAlgorithm() + public BidiAlgorithm() { } @@ -1272,7 +1273,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Locate all pair brackets in the current isolating run /// /// A sorted list of BracketPairs - private List LocatePairedBrackets() + private PooledList LocatePairedBrackets() { // Clear work collections _pendingOpeningBrackets.Clear(); @@ -1719,8 +1720,13 @@ namespace Avalonia.Media.TextFormatting.Unicode { _workingClassesBuffer.Dispose(); _resolvedLevelsBuffer.Dispose(); + _statusStack.Dispose(); _x9Map.Dispose(); + _levelRuns.Dispose(); _isolatedRunMapping.Dispose(); + _pendingIsolateOpenings.Dispose(); + _pendingOpeningBrackets.Dispose(); + _pairedBrackets.Dispose(); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs index 7e5b3a6620..644f7e9a8a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Represents a unicode string and all associated attributes /// for each character required for the bidirectional Unicode algorithm /// - internal class BidiData : IDisposable + internal struct BidiData : IDisposable { private ArrayBuilder _classes; private ArrayBuilder _pairedBracketTypes; From 22a41933bedfddb81c1d4db82a078263e7632a0c Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 03:43:55 +0300 Subject: [PATCH 198/213] [Text] bidi - do not use pooled collections --- .../TextFormatting/Unicode/BiDiAlgorithm.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 0bea4def28..093c7f3f56 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -98,7 +98,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The status stack used during resolution of explicit /// embedding and isolating runs /// - private readonly PooledStack _statusStack = new PooledStack(); + private readonly Stack _statusStack = new Stack(); /// /// Mapping used to virtually remove characters for rule X9 @@ -108,7 +108,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// Re-usable list of level runs /// - private readonly PooledList _levelRuns = new PooledList(); + private readonly List _levelRuns = new List(); /// /// Mapping for the current isolating sequence, built @@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// A stack of pending isolate openings used by FindIsolatePairs() /// - private readonly PooledStack _pendingIsolateOpenings = new PooledStack(); + private readonly Stack _pendingIsolateOpenings = new Stack(); /// /// The level of the isolating run currently being processed @@ -175,12 +175,12 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Reusable list of pending opening brackets used by the /// LocatePairedBrackets method /// - private readonly PooledList _pendingOpeningBrackets = new PooledList(); + private readonly List _pendingOpeningBrackets = new List(); /// /// Resolved list of paired brackets /// - private readonly PooledList _pairedBrackets = new PooledList(); + private readonly List _pairedBrackets = new List(); /// /// Initializes a new instance of the class. @@ -1273,7 +1273,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Locate all pair brackets in the current isolating run /// /// A sorted list of BracketPairs - private PooledList LocatePairedBrackets() + private List LocatePairedBrackets() { // Clear work collections _pendingOpeningBrackets.Clear(); @@ -1720,13 +1720,8 @@ namespace Avalonia.Media.TextFormatting.Unicode { _workingClassesBuffer.Dispose(); _resolvedLevelsBuffer.Dispose(); - _statusStack.Dispose(); _x9Map.Dispose(); - _levelRuns.Dispose(); _isolatedRunMapping.Dispose(); - _pendingIsolateOpenings.Dispose(); - _pendingOpeningBrackets.Dispose(); - _pairedBrackets.Dispose(); } } } From 8ece18c53dcedf2475f9cbf97a25c4ea66f564e5 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 04:09:19 +0300 Subject: [PATCH 199/213] [Text] optimize CoalesceLevels alloc --- .../Media/TextFormatting/TextFormatterImpl.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index a69c56eee1..517372648f 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -187,10 +187,7 @@ namespace Avalonia.Media.TextFormatting var processedRuns = new List(textRuns.Count); - foreach (var coalescedRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels)) - { - processedRuns.AddRange(coalescedRuns); - } + CoalesceLevels(textRuns, biDi.ResolvedLevels, processedRuns); for (var index = 0; index < processedRuns.Count; index++) { @@ -282,12 +279,14 @@ namespace Avalonia.Media.TextFormatting /// /// The text characters to form from. /// The bidi levels. + /// /// - private static IEnumerable> CoalesceLevels(IReadOnlyList textCharacters, ArraySlice levels) + private static void CoalesceLevels(IReadOnlyList textCharacters, ArraySlice levels, + List processedRuns) { if (levels.Length == 0) { - yield break; + return; } var levelIndex = 0; @@ -306,7 +305,7 @@ namespace Avalonia.Media.TextFormatting { var drawableRun = textCharacters[i]; - yield return new[] { drawableRun }; + processedRuns.Add(drawableRun); levelIndex += drawableRun.Length; @@ -329,7 +328,7 @@ namespace Avalonia.Media.TextFormatting if (j == runText.Length) { - yield return currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties); + processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties)); runLevel = levels[levelIndex]; @@ -342,7 +341,7 @@ namespace Avalonia.Media.TextFormatting } // End of this run - yield return currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties); + processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties)); runText = runText.Skip(j); @@ -355,10 +354,10 @@ namespace Avalonia.Media.TextFormatting if (currentRun is null || runText.IsEmpty) { - yield break; + return; } - yield return currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties); + processedRuns.AddRange(currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties)); } /// From 31beefe487a3f3d09027d835c7897d0d082947f6 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 04:12:02 +0300 Subject: [PATCH 200/213] [Bidi] Optimize alloc of double dictionary --- .../Media/TextFormatting/Unicode/BiDiAlgorithm.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 093c7f3f56..8014762ce8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -67,7 +67,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The forward mapping maps the start index to the end index. /// The reverse mapping maps the end index to the start index. /// - private readonly BidiDictionary _isolatePairs = new BidiDictionary(); + private BidiDictionary? _isolatePairs; /// /// The working BiDi classes @@ -228,7 +228,7 @@ namespace Avalonia.Media.TextFormatting.Unicode ArraySlice? outLevels) { // Reset state - _isolatePairs.Clear(); + _isolatePairs?.Clear(); _workingClassesBuffer.Clear(); _levelRuns.Clear(); _resolvedLevelsBuffer.Clear(); @@ -324,7 +324,7 @@ namespace Avalonia.Media.TextFormatting.Unicode // Skip isolate pairs // (Because we're working with a slice, we need to adjust the indices // we're using for the isolatePairs map) - if (_isolatePairs.TryGetValue(data.Start + i, out i)) + if (_isolatePairs?.TryGetValue(data.Start + i, out i) == true) { i -= data.Start; } @@ -379,6 +379,7 @@ namespace Avalonia.Media.TextFormatting.Unicode { if (_pendingIsolateOpenings.Count > 0) { + _isolatePairs ??= new BidiDictionary(); _isolatePairs.Add(_pendingIsolateOpenings.Pop(), i); } @@ -499,7 +500,7 @@ namespace Avalonia.Media.TextFormatting.Unicode if (resolvedIsolate == BidiClass.FirstStrongIsolate) { - if (!_isolatePairs.TryGetValue(i, out var endOfIsolate)) + if (_isolatePairs == null || !_isolatePairs.TryGetValue(i, out var endOfIsolate)) { endOfIsolate = _originalClasses.Length; } @@ -830,7 +831,7 @@ namespace Avalonia.Media.TextFormatting.Unicode var lastCharacterIndex = _isolatedRunMapping[_isolatedRunMapping.Length - 1]; var lastType = _originalClasses[lastCharacterIndex]; if ((lastType == BidiClass.LeftToRightIsolate || lastType == BidiClass.RightToLeftIsolate || lastType == BidiClass.FirstStrongIsolate) && - _isolatePairs.TryGetValue(lastCharacterIndex, out var nextRunIndex)) + _isolatePairs?.TryGetValue(lastCharacterIndex, out var nextRunIndex) == true) { // Find the continuing run index runIndex = FindRunForIndex(nextRunIndex); From 97f2a26941c492cd7bcc5447dd78aac60ce12b80 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 04:18:01 +0300 Subject: [PATCH 201/213] [Text] [Bidi] do not create collections if not need them --- .../Media/TextFormatting/Unicode/BiDiAlgorithm.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 8014762ce8..100d381afe 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// A stack of pending isolate openings used by FindIsolatePairs() /// - private readonly Stack _pendingIsolateOpenings = new Stack(); + private Stack? _pendingIsolateOpenings; /// /// The level of the isolating run currently being processed @@ -359,7 +359,7 @@ namespace Avalonia.Media.TextFormatting.Unicode _hasIsolates = false; // BD9... - _pendingIsolateOpenings.Clear(); + _pendingIsolateOpenings?.Clear(); for (var i = 0; i < _originalClasses.Length; i++) { @@ -371,13 +371,14 @@ namespace Avalonia.Media.TextFormatting.Unicode case BidiClass.RightToLeftIsolate: case BidiClass.FirstStrongIsolate: { + _pendingIsolateOpenings ??= new Stack(); _pendingIsolateOpenings.Push(i); _hasIsolates = true; break; } case BidiClass.PopDirectionalIsolate: { - if (_pendingIsolateOpenings.Count > 0) + if (_pendingIsolateOpenings?.Count > 0) { _isolatePairs ??= new BidiDictionary(); _isolatePairs.Add(_pendingIsolateOpenings.Pop(), i); From fc4e549c2eb480aa1339befc88a6473bd71a320c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 11 Jan 2023 13:31:01 +0100 Subject: [PATCH 202/213] Cleanup --- src/Avalonia.Base/Media/TextDecoration.cs | 57 ++++++++++++++++++- src/Avalonia.Base/Platform/IGlyphRunImpl.cs | 6 +- .../HeadlessPlatformRenderInterface.cs | 5 ++ src/Skia/Avalonia.Skia/GlyphRunImpl.cs | 4 ++ .../Avalonia.Direct2D1/Media/GlyphRunImpl.cs | 8 ++- tests/Avalonia.UnitTests/MockGlyphRun.cs | 8 ++- 6 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index 0a7328125a..0ed73e7786 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -1,5 +1,9 @@ -using Avalonia.Collections; +using System.Collections.Generic; +using Avalonia.Collections; +using Avalonia.Collections.Pooled; using Avalonia.Media.TextFormatting; +using Avalonia.Platform; +using Avalonia.Utilities; namespace Avalonia.Media { @@ -196,19 +200,66 @@ namespace Avalonia.Media break; } + var strokeOffset = 0.0; + switch (StrokeOffsetUnit) { case TextDecorationUnit.FontRenderingEmSize: - origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize); + strokeOffset = StrokeOffset * textMetrics.FontRenderingEmSize; break; case TextDecorationUnit.Pixel: - origin += new Point(0, StrokeOffset); + strokeOffset = StrokeOffset; break; } + origin += new Point(0, strokeOffset); + var pen = new Pen(Stroke ?? defaultBrush, thickness, new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap); + if (Location == TextDecorationLocation.Underline && MathUtilities.IsZero(strokeOffset)) + { + var intersections = glyphRun.GlyphRunImpl.GetIntersections((float)(thickness * 0.5d + strokeOffset), (float)(thickness * 1.5d + strokeOffset)); + + if (intersections != null && intersections.Count > 0) + { + var last = baselineOrigin.X; + var finalPos = last + glyphRun.Size.Width; + var end = last; + + var points = new List(); + + //math is taken from chrome's source code. + for (var i = 0; i < intersections.Count; i += 2) + { + var start = intersections[i] - thickness; + end = intersections[i + 1] + thickness; + if (start > last && last + textMetrics.FontRenderingEmSize / 12 < start) + { + points.Add(last); + points.Add(start); + } + last = end; + } + + if (end < finalPos) + { + points.Add(end); + points.Add(finalPos); + } + + for (var i = 0; i < points.Count; i += 2) + { + var a = new Point(points[i], origin.Y); + var b = new Point(points[i + 1], origin.Y); + + drawingContext.DrawLine(pen, a, b); + } + + return; + } + } + drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Metrics.Width, 0)); } } diff --git a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs index 7801bdd50f..6a8ae4d954 100644 --- a/src/Avalonia.Base/Platform/IGlyphRunImpl.cs +++ b/src/Avalonia.Base/Platform/IGlyphRunImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Metadata; namespace Avalonia.Platform @@ -7,5 +8,8 @@ namespace Avalonia.Platform /// Actual implementation of a glyph run that stores platform dependent resources. /// [Unstable] - public interface IGlyphRunImpl : IDisposable { } + public interface IGlyphRunImpl : IDisposable + { + IReadOnlyList GetIntersections(float lowerBound, float upperBound); + } } diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 9bb4b34976..cb79cc85db 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -128,6 +128,11 @@ namespace Avalonia.Headless public void Dispose() { } + + public IReadOnlyList GetIntersections(float lowerBound, float upperBound) + { + throw new NotImplementedException(); + } } class HeadlessGeometryStub : IGeometryImpl diff --git a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs index 484fd9f219..dd7ed31a6e 100644 --- a/src/Skia/Avalonia.Skia/GlyphRunImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphRunImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Metadata; using Avalonia.Platform; using SkiaSharp; @@ -20,6 +21,9 @@ namespace Avalonia.Skia /// public SKTextBlob TextBlob { get; } + public IReadOnlyList GetIntersections(float upperBound, float lowerBound) => + TextBlob.GetIntercepts(lowerBound, upperBound); + void IDisposable.Dispose() { TextBlob.Dispose(); diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs index 0b06d5ef3e..638aaf08d6 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs @@ -1,4 +1,5 @@ -using Avalonia.Platform; +using System.Collections.Generic; +using Avalonia.Platform; namespace Avalonia.Direct2D1.Media { @@ -15,5 +16,10 @@ namespace Avalonia.Direct2D1.Media { GlyphRun?.Dispose(); } + + public IReadOnlyList GetIntersections(float lowerBound, float upperBound) + { + return null; + } } } diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs index 24948aff01..f525e4736b 100644 --- a/tests/Avalonia.UnitTests/MockGlyphRun.cs +++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs @@ -1,4 +1,5 @@ -using Avalonia.Platform; +using System.Collections.Generic; +using Avalonia.Platform; namespace Avalonia.UnitTests { @@ -8,5 +9,10 @@ namespace Avalonia.UnitTests { } + + public IReadOnlyList GetIntersections(float lowerBound, float upperBound) + { + return null; + } } } From f6535c6b26845f7e327ab494e5ff50b53fb108d8 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 16:01:37 +0300 Subject: [PATCH 203/213] [Text] Dispose Text Layout in TextBlock and TextPresenter --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 3 +++ src/Avalonia.Controls/TextBlock.cs | 11 ++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 480a8ee7a7..f599511392 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -588,6 +588,7 @@ namespace Avalonia.Controls.Presenters protected virtual void InvalidateTextLayout() { + _textLayout?.Dispose(); _textLayout = null; InvalidateMeasure(); @@ -597,6 +598,7 @@ namespace Avalonia.Controls.Presenters { _constraint = availableSize; + _textLayout?.Dispose(); _textLayout = null; InvalidateArrange(); @@ -622,6 +624,7 @@ namespace Avalonia.Controls.Presenters _constraint = new Size(Math.Ceiling(finalSize.Width), double.PositiveInfinity); + _textLayout?.Dispose(); _textLayout = null; return finalSize; diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 7a0da1ecc5..31ddf1f985 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -173,13 +173,7 @@ namespace Avalonia.Controls /// /// Gets the used to render the text. /// - public TextLayout TextLayout - { - get - { - return _textLayout ??= CreateTextLayout(_text); - } - } + public TextLayout TextLayout => _textLayout ??= CreateTextLayout(_text); /// /// Gets or sets the padding to place around the . @@ -648,6 +642,7 @@ namespace Avalonia.Controls /// protected void InvalidateTextLayout() { + _textLayout?.Dispose(); _textLayout = null; InvalidateVisual(); @@ -663,6 +658,7 @@ namespace Avalonia.Controls _constraint = availableSize.Deflate(padding); + _textLayout?.Dispose(); _textLayout = null; var inlines = Inlines; @@ -726,6 +722,7 @@ namespace Avalonia.Controls _constraint = new Size(Math.Ceiling(finalSize.Deflate(padding).Width), double.PositiveInfinity); + _textLayout?.Dispose(); _textLayout = null; if (HasComplexContent) From 56e94e424c9b120f8fac3aa450bbe43ceae22d81 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 16:35:23 +0300 Subject: [PATCH 204/213] [Text] Add textblocks virtualization benchmark, rename field --- .../Media/TextFormatting/ShapedBuffer.cs | 8 +++---- .../Text/HugeTextLayout.cs | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 5cf153d199..40500670ad 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -8,12 +8,12 @@ namespace Avalonia.Media.TextFormatting public sealed class ShapedBuffer : IList, IDisposable { private static readonly IComparer s_clusterComparer = new CompareClusters(); - private bool _rented; + private bool _bufferRented; public ShapedBuffer(CharacterBufferRange characterBufferRange, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : this(characterBufferRange, ArrayPool.Shared.Rent(bufferLength), glyphTypeface, fontRenderingEmSize, bidiLevel) { - _rented = true; + _bufferRented = true; Length = bufferLength; } @@ -268,7 +268,7 @@ namespace Avalonia.Media.TextFormatting public void Dispose() { GC.SuppressFinalize(this); - if (_rented) + if (_bufferRented) { GlyphInfos.ReturnRent(); } @@ -276,7 +276,7 @@ namespace Avalonia.Media.TextFormatting ~ShapedBuffer() { - if (_rented) + if (_bufferRented) { GlyphInfos.ReturnRent(); } diff --git a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs index 20fa268a85..bf0253e9ab 100644 --- a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs +++ b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Avalonia.Controls; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.UnitTests; @@ -70,6 +71,26 @@ In respect that the structure of the sufficient amount poses problems and challe [Benchmark] public TextLayout[] BuildManySmallTexts() => _manySmallStrings.Select(MakeLayout).ToArray(); + [Benchmark] + public void VirtualizeTextBlocks() + { + var blocks = new TextBlock[10]; + for (var i = 0; i < blocks.Length; i++) + { + blocks[i] = new TextBlock + { + Width = 120, + Height = 32, + }; + } + + for (int i = 0, j = 0; i < _manySmallStrings.Length; i++, j = j < blocks.Length - 1 ? j + 1 : 0) + { + blocks[j].Text = _manySmallStrings[i]; + blocks[j].Measure(new Size(200, 200)); + } + } + private static TextLayout MakeLayout(string str) { var layout = new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth: 120); From 91fd89017e3f0b43886422715b091c42cacf034b Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 17:34:48 +0300 Subject: [PATCH 205/213] [Text] do not use linq on dispose --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index af954eb5e9..6034e9f679 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -981,9 +981,12 @@ namespace Avalonia.Media.TextFormatting public override void Dispose() { - foreach (var run in _textRuns.OfType()) + for (int i = 0; i < _textRuns.Count; i++) { - run.Dispose(); + if (_textRuns[i] is ShapedTextRun shapedTextRun) + { + shapedTextRun.Dispose(); + } } } From 74bbfd8509c34c82d7f73f9bd1e85f9e1801414a Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 11 Jan 2023 15:35:07 +0100 Subject: [PATCH 206/213] Support intercepts for all location but TextDecorationLocation.Strikethrough --- src/Avalonia.Base/Media/TextDecoration.cs | 19 +++++++++---------- tests/Avalonia.Benchmarks/NullGlyphRun.cs | 8 +++++++- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index 0ed73e7786..d6b2841214 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Collections; using Avalonia.Collections.Pooled; using Avalonia.Media.TextFormatting; @@ -200,26 +201,24 @@ namespace Avalonia.Media break; } - var strokeOffset = 0.0; - switch (StrokeOffsetUnit) { case TextDecorationUnit.FontRenderingEmSize: - strokeOffset = StrokeOffset * textMetrics.FontRenderingEmSize; + origin += new Point(0, StrokeOffset * textMetrics.FontRenderingEmSize); break; case TextDecorationUnit.Pixel: - strokeOffset = StrokeOffset; + origin += new Point(0, StrokeOffset); break; } - origin += new Point(0, strokeOffset); - var pen = new Pen(Stroke ?? defaultBrush, thickness, new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap); - if (Location == TextDecorationLocation.Underline && MathUtilities.IsZero(strokeOffset)) + if (Location != TextDecorationLocation.Strikethrough) { - var intersections = glyphRun.GlyphRunImpl.GetIntersections((float)(thickness * 0.5d + strokeOffset), (float)(thickness * 1.5d + strokeOffset)); + var offsetY = glyphRun.BaselineOrigin.Y - origin.Y; + + var intersections = glyphRun.GlyphRunImpl.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); if (intersections != null && intersections.Count > 0) { @@ -257,7 +256,7 @@ namespace Avalonia.Media } return; - } + } } drawingContext.DrawLine(pen, origin, origin + new Point(glyphRun.Metrics.Width, 0)); diff --git a/tests/Avalonia.Benchmarks/NullGlyphRun.cs b/tests/Avalonia.Benchmarks/NullGlyphRun.cs index 5ba0822649..1c2c7c0d7d 100644 --- a/tests/Avalonia.Benchmarks/NullGlyphRun.cs +++ b/tests/Avalonia.Benchmarks/NullGlyphRun.cs @@ -1,4 +1,5 @@ -using Avalonia.Platform; +using System.Collections.Generic; +using Avalonia.Platform; namespace Avalonia.Benchmarks { @@ -7,5 +8,10 @@ namespace Avalonia.Benchmarks public void Dispose() { } + + public IReadOnlyList GetIntersections(float lowerBound, float upperBound) + { + return null; + } } } From 13280eecf51d2055936d48684643b232d5f8277c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 11 Jan 2023 16:29:01 +0100 Subject: [PATCH 207/213] Fix TextLineMetrics calculation for TextLines that contain a TextEndOfLine run --- .../Media/TextFormatting/TextLineImpl.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index a1f93bcd07..e7efa33435 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1436,6 +1436,13 @@ namespace Avalonia.Media.TextFormatting var lineHeight = _paragraphProperties.LineHeight; + var lastRunIndex = _textRuns.Count - 1; + + if (_textRuns[lastRunIndex] is TextEndOfLine && lastRunIndex > 0) + { + lastRunIndex--; + } + for (var index = 0; index < _textRuns.Count; index++) { switch (_textRuns[index]) @@ -1470,7 +1477,7 @@ namespace Avalonia.Media.TextFormatting } } - if (index == _textRuns.Count - 1) + if (index == lastRunIndex) { width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width; trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength; @@ -1490,7 +1497,7 @@ namespace Avalonia.Media.TextFormatting { case FlowDirection.LeftToRight: { - if (index == _textRuns.Count - 1) + if (index == lastRunIndex) { width = widthIncludingWhitespace; trailingWhitespaceLength = 0; @@ -1502,7 +1509,7 @@ namespace Avalonia.Media.TextFormatting case FlowDirection.RightToLeft: { - if (index == _textRuns.Count - 1) + if (index == lastRunIndex) { width = widthIncludingWhitespace; trailingWhitespaceLength = 0; From 55f33e5c444caef23e9805f4af5b51d37f099883 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 20:21:00 +0300 Subject: [PATCH 208/213] [Text] fix tests for ShapedBuffer pooling --- src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs index 5cf153d199..0e3267ee38 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedBuffer.cs @@ -11,7 +11,11 @@ namespace Avalonia.Media.TextFormatting private bool _rented; public ShapedBuffer(CharacterBufferRange characterBufferRange, int bufferLength, IGlyphTypeface glyphTypeface, double fontRenderingEmSize, sbyte bidiLevel) : - this(characterBufferRange, ArrayPool.Shared.Rent(bufferLength), glyphTypeface, fontRenderingEmSize, bidiLevel) + this(characterBufferRange, + new ArraySlice(ArrayPool.Shared.Rent(bufferLength), 0, bufferLength), + glyphTypeface, + fontRenderingEmSize, + bidiLevel) { _rented = true; Length = bufferLength; From 1e646fd9a0431fa1c5b72f384232cff118f6c2e4 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 11 Jan 2023 18:47:30 +0100 Subject: [PATCH 209/213] Prevent a crash for empty lines --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index e7efa33435..72a8ec8941 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -1438,7 +1438,7 @@ namespace Avalonia.Media.TextFormatting var lastRunIndex = _textRuns.Count - 1; - if (_textRuns[lastRunIndex] is TextEndOfLine && lastRunIndex > 0) + if (lastRunIndex > 0 && _textRuns[lastRunIndex] is TextEndOfLine) { lastRunIndex--; } From 95ccb0c9c69dda3873a2a1ec1e0ce8f922f1d8c2 Mon Sep 17 00:00:00 2001 From: Martijn Sneijders Date: Thu, 12 Jan 2023 20:24:28 +0100 Subject: [PATCH 210/213] ScrollGestureRecognizer ends its current fling gesture (touch or pen) if ScrollGestureEventArgs is no longer handled by the Presenter. --- .../Input/GestureRecognizers/ScrollGestureRecognizer.cs | 9 ++++++++- src/Avalonia.Base/Input/ScrollGestureEventArgs.cs | 4 ++++ .../Presenters/ScrollContentPresenter.cs | 2 ++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index cec98ec66b..790439245a 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -189,7 +189,14 @@ namespace Avalonia.Input.GestureRecognizers var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds); var distance = speed * elapsedSinceLastTick.TotalSeconds; - _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); + var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance); + _target!.RaiseEvent(scrollGestureEventArgs); + + if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture) + { + EndGesture(); + return false; + } // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) diff --git a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs index fd1d0f42c3..f1a0887b60 100644 --- a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs +++ b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs @@ -6,6 +6,10 @@ namespace Avalonia.Input { public int Id { get; } public Vector Delta { get; } + /// + /// When set the ScrollGestureRecognizer should stop its current active scroll gesture. + /// + public bool ShouldEndScrollGesture { get; set; } private static int _nextId = 1; public static int GetNextFreeId() => _nextId++; diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 5a691ce665..0cfe4bada1 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -428,6 +428,8 @@ namespace Avalonia.Controls.Presenters Offset = newOffset; e.Handled = !IsScrollChainingEnabled || offsetChanged; + + e.ShouldEndScrollGesture = !IsScrollChainingEnabled && !offsetChanged; } } From 79b30658f15b11820a5a086923d3fed2bbdf2a07 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 13 Jan 2023 10:19:45 +0000 Subject: [PATCH 211/213] add TopLevel.GetTopLevel api --- samples/ControlCatalog/Pages/DialogsPage.xaml.cs | 2 +- samples/ControlCatalog/Pages/NotificationsPage.xaml.cs | 2 +- src/Avalonia.Controls/ComboBox.cs | 2 +- src/Avalonia.Controls/Primitives/OverlayPopupHost.cs | 2 +- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 10 ++++++++++ src/Avalonia.Native/AvaloniaNativeDragSource.cs | 3 +-- src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs | 3 +-- 8 files changed, 17 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 67e9ef4e40..46f25f4d8d 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -397,7 +397,7 @@ CanPickFolder: {storageProvider.CanPickFolder}"; } Window GetWindow() => this.VisualRoot as Window ?? throw new NullReferenceException("Invalid Owner"); - TopLevel GetTopLevel() => this.VisualRoot as TopLevel ?? throw new NullReferenceException("Invalid Owner"); + TopLevel GetTopLevel() => TopLevel.GetTopLevel(this) ?? throw new NullReferenceException("Invalid Owner"); private void InitializeComponent() { diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs index bfd49a2c00..6f83e5c366 100644 --- a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs @@ -27,7 +27,7 @@ namespace ControlCatalog.Pages { base.OnAttachedToVisualTree(e); - _viewModel.NotificationManager = new Avalonia.Controls.Notifications.WindowNotificationManager(VisualRoot as TopLevel); + _viewModel.NotificationManager = new Avalonia.Controls.Notifications.WindowNotificationManager(TopLevel.GetTopLevel(this)); } } } diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index 8da99b5f25..6018779e34 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -383,7 +383,7 @@ namespace Avalonia.Controls _subscriptionsOnOpen.Clear(); - var toplevel = this.GetVisualRoot() as TopLevel; + var toplevel = TopLevel.GetTopLevel(this); if (toplevel != null) { toplevel.AddDisposableHandler(PointerWheelChangedEvent, (s, ev) => diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index e0f72cae54..e265f4eb6a 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -123,7 +123,7 @@ namespace Avalonia.Controls.Primitives public static IPopupHost CreatePopupHost(Visual target, IAvaloniaDependencyResolver? dependencyResolver) { - var platform = (target.GetVisualRoot() as TopLevel)?.PlatformImpl?.CreatePopup(); + var platform = TopLevel.GetTopLevel(target)?.PlatformImpl?.CreatePopup(); if (platform != null) return new PopupRoot((TopLevel)target.GetVisualRoot()!, platform, dependencyResolver); diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index bf79198939..7cac12eabe 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -358,7 +358,7 @@ namespace Avalonia.Controls.Primitives return; } - var topLevel = placementTarget.VisualRoot as TopLevel; + var topLevel = TopLevel.GetTopLevel(placementTarget); if (topLevel == null) { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 1f5b8f80d2..9970a0f1c7 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -341,6 +341,16 @@ namespace Avalonia.Controls { return PlatformImpl?.PointToScreen(p) ?? default; } + + /// + /// Gets the for which the given is hosted in. + /// + /// The visual to query its TopLevel + /// The TopLevel + public static TopLevel? GetTopLevel(Visual? visual) + { + return visual == null ? null : visual.VisualRoot as TopLevel; + } /// /// Creates the layout manager for this . diff --git a/src/Avalonia.Native/AvaloniaNativeDragSource.cs b/src/Avalonia.Native/AvaloniaNativeDragSource.cs index bec45c2d71..7f4c462ee0 100644 --- a/src/Avalonia.Native/AvaloniaNativeDragSource.cs +++ b/src/Avalonia.Native/AvaloniaNativeDragSource.cs @@ -6,7 +6,6 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Native.Interop; -using Avalonia.VisualTree; namespace Avalonia.Native { @@ -26,7 +25,7 @@ namespace Avalonia.Native if (element == null) return null; var visual = (Visual)element; - return visual.GetVisualRoot() as TopLevel; + return TopLevel.GetTopLevel(visual); } class DndCallback : NativeCallbackBase, IAvnDndResultCallback diff --git a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs index cf1bdc1671..998ff4a427 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsMouseDevice.cs @@ -1,7 +1,6 @@ using System; using Avalonia.Controls; using Avalonia.Input; -using Avalonia.VisualTree; using Avalonia.Win32.Interop; namespace Avalonia.Win32.Input @@ -34,7 +33,7 @@ namespace Avalonia.Win32.Input protected override void PlatformCapture(IInputElement element) { - var hwnd = (((element as Visual)?.GetVisualRoot() as TopLevel)?.PlatformImpl as WindowImpl) + var hwnd = (TopLevel.GetTopLevel(element as Visual)?.PlatformImpl as WindowImpl) ?.Handle.Handle; if (hwnd.HasValue && hwnd != IntPtr.Zero) From 7cd51c91615e3b02e8aa0827d179e77c315db89c Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 13 Jan 2023 14:34:22 -0500 Subject: [PATCH 212/213] Update samples/ControlCatalog/Pages/DialogsPage.xaml.cs --- samples/ControlCatalog/Pages/DialogsPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 46f25f4d8d..8db6e76dca 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -396,7 +396,7 @@ CanPickFolder: {storageProvider.CanPickFolder}"; return item.TryGetUri(out var uri) ? uri.ToString() : item.Name; } - Window GetWindow() => this.VisualRoot as Window ?? throw new NullReferenceException("Invalid Owner"); + Window GetWindow() => TopLevel.GetTopLevel(this) as Window ?? throw new NullReferenceException("Invalid Owner"); TopLevel GetTopLevel() => TopLevel.GetTopLevel(this) ?? throw new NullReferenceException("Invalid Owner"); private void InitializeComponent() From 473cda2d3e92e511a4f1917d18c5cb641394b426 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 13 Jan 2023 21:56:10 +0100 Subject: [PATCH 213/213] [Direct2D1] Don't dispose DirectWrite GlyphRun explicitly --- src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs index 638aaf08d6..67418613a4 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs @@ -14,7 +14,8 @@ namespace Avalonia.Direct2D1.Media public void Dispose() { - GlyphRun?.Dispose(); + //SharpDX already handles this. + //GlyphRun?.Dispose(); } public IReadOnlyList GetIntersections(float lowerBound, float upperBound)