From 493128651fdefdd8b26b589c355f60ea7544f352 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 22 Jul 2022 13:24:48 +0200 Subject: [PATCH 001/122] Add x:SetterTargetType --- .../AvaloniaXamlIlSetterTransformer.cs | 55 +++++++++++++++++-- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index 6da95be1c1..d6251badd5 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using XamlX; using XamlX.Ast; using XamlX.Emit; using XamlX.IL; @@ -17,10 +18,55 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers && on.Type.GetClrType().FullName == "Avalonia.Styling.Setter")) return node; - var targetTypeNode = context.ParentNodes() + IXamlType targetType = null; + IXamlLineInfo lineInfo = null; + + var styleParent = context.ParentNodes() .OfType() - .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ?? - throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node); + .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + + if (styleParent != null) + { + var selectorProperty = styleParent.Children.OfType() + .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Selector"); + + if (selectorProperty == null) + throw new XamlParseException( + "Can not find parent Style Selector", node); + + var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; + + targetType = selector?.TargetType + ?? throw new XamlParseException("Can not resolve parent Style Selector type", node); + lineInfo = selector; + } + else + { + foreach (var p in context.ParentNodes().OfType()) + { + for (var index = 0; index < p.Children.Count; index++) + { + if (p.Children[index] is XamlAstXmlDirective d && + d.Namespace == XamlNamespaces.Xaml2006 && + d.Name == "SetterTargetType") + { + //p.Children.RemoveAt(index); + + targetType = context.Configuration.TypeSystem.GetType(((XamlAstTextNode)d.Values[0]).Text); + lineInfo = d; + + break; + } + } + + if (targetType != null) break; + } + } + + if (targetType == null) + { + throw new XamlParseException("Could not determine target type of Setter", node); + } IXamlType propType = null; var property = @on.Children.OfType() @@ -31,9 +77,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (propertyName == null) throw new XamlParseException("Setter.Property must be a string", node); - var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName, - new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]); + new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]); property.Values = new List {avaloniaPropertyNode}; propType = avaloniaPropertyNode.AvaloniaPropertyType; } From 6820d019d947870699346f78a0ace186d551cf97 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Mon, 25 Jul 2022 11:12:40 +0200 Subject: [PATCH 002/122] Fix merge --- .../Transformers/AvaloniaXamlIlSetterTransformer.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index d6251badd5..ddfba5eab2 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -27,18 +27,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers if (styleParent != null) { - var selectorProperty = styleParent.Children.OfType() - .FirstOrDefault(x => x.Property.GetClrProperty().Name == "Selector"); - - if (selectorProperty == null) - throw new XamlParseException( - "Can not find parent Style Selector", node); - - var selector = selectorProperty.Values.FirstOrDefault() as XamlIlSelectorNode; - - targetType = selector?.TargetType + targetType = styleParent.TargetType.GetClrType() ?? throw new XamlParseException("Can not resolve parent Style Selector type", node); - lineInfo = selector; + lineInfo = on; } else { From 04b7a8b0c19bf1d63ee3bb9ee842f61d0c56c0b5 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 26 Jul 2022 11:11:39 +0200 Subject: [PATCH 003/122] Add UT --- .../SetterTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs new file mode 100644 index 0000000000..229fa3aa7d --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs @@ -0,0 +1,29 @@ +using System.Linq; +using Avalonia.Data; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests +{ + public class SetterTests : XamlTestBase + { + [Fact] + public void Setter_Should_Work_Outside_Of_Style_With_SetterTargetType_Attribute() + { + using (UnitTestApplication.Start(TestServices.MockPlatformWrapper)) + { + var xaml = @" + + + + +"; + var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml); + var setter = (Setter)animation.Children[0].Setters[0]; + + Assert.IsType(setter.Value); + } + } + } +} From cfad3051aa49fdd15bc0a433732e3fa3aa0f5313 Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Tue, 26 Jul 2022 11:12:14 +0200 Subject: [PATCH 004/122] RemoveChild for now --- .../Transformers/AvaloniaXamlIlSetterTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index ddfba5eab2..d69e86e409 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -41,7 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "SetterTargetType") { - //p.Children.RemoveAt(index); + p.Children.RemoveAt(index); targetType = context.Configuration.TypeSystem.GetType(((XamlAstTextNode)d.Values[0]).Text); lineInfo = d; From f6b50e5fde80dd0fb172f07e958dd4739975be0e Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 27 Jul 2022 11:10:39 +0200 Subject: [PATCH 005/122] Use StyledWindow services --- tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs index 229fa3aa7d..357245b826 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs @@ -11,7 +11,7 @@ namespace Avalonia.Markup.Xaml.UnitTests [Fact] public void Setter_Should_Work_Outside_Of_Style_With_SetterTargetType_Attribute() { - using (UnitTestApplication.Start(TestServices.MockPlatformWrapper)) + using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = @" From fdf852a60cfb8baf4aa19cd855220a54f520886b Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Wed, 27 Jul 2022 11:12:13 +0200 Subject: [PATCH 006/122] Don't remove node --- .../Transformers/AvaloniaXamlIlSetterTransformer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs index d69e86e409..8c96ed96a9 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -41,8 +41,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers d.Namespace == XamlNamespaces.Xaml2006 && d.Name == "SetterTargetType") { - p.Children.RemoveAt(index); - targetType = context.Configuration.TypeSystem.GetType(((XamlAstTextNode)d.Values[0]).Text); lineInfo = d; From 8cae9556888dcc1469de061c0a554cb906a1eb6c Mon Sep 17 00:00:00 2001 From: Luis von der Eltz Date: Fri, 29 Jul 2022 11:16:28 +0200 Subject: [PATCH 007/122] Add another keyframe to UT --- tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs index 357245b826..cc1dce4de8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs @@ -18,6 +18,9 @@ namespace Avalonia.Markup.Xaml.UnitTests + + + "; var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml); var setter = (Setter)animation.Children[0].Setters[0]; From 889001dcaf5606b23f3991979738eb8492354d2c Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 30 Aug 2022 16:49:01 +0300 Subject: [PATCH 008/122] Remove public constructors for KeyEventArgs, PointerEventArgs and friends. --- src/Avalonia.Base/Controls/NameScopeEventArgs.cs | 16 ---------------- src/Avalonia.Base/Input/DragEventArgs.cs | 2 +- src/Avalonia.Base/Input/GotFocusEventArgs.cs | 5 +++++ src/Avalonia.Base/Input/KeyEventArgs.cs | 5 +++++ src/Avalonia.Base/Input/PointerDeltaEventArgs.cs | 2 +- src/Avalonia.Base/Input/PointerEventArgs.cs | 12 ++++++------ src/Avalonia.Base/Input/PointerWheelEventArgs.cs | 2 +- .../Input/ScrollGestureEventArgs.cs | 6 +++--- src/Avalonia.Base/Input/TappedEventArgs.cs | 2 +- src/Avalonia.Base/Input/TextInputEventArgs.cs | 4 ++++ src/Avalonia.Base/Input/VectorEventArgs.cs | 5 +++++ .../Layout/EffectiveViewportChangedEventArgs.cs | 2 +- .../Rendering/SceneInvalidatedEventArgs.cs | 2 +- 13 files changed, 34 insertions(+), 31 deletions(-) delete mode 100644 src/Avalonia.Base/Controls/NameScopeEventArgs.cs diff --git a/src/Avalonia.Base/Controls/NameScopeEventArgs.cs b/src/Avalonia.Base/Controls/NameScopeEventArgs.cs deleted file mode 100644 index 3e9eaa6057..0000000000 --- a/src/Avalonia.Base/Controls/NameScopeEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Avalonia.Controls -{ - public class NameScopeEventArgs : EventArgs - { - public NameScopeEventArgs(string name, object element) - { - Name = name; - Element = element; - } - - public string Name { get; } - public object Element { get; } - } -} diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 0e613c0f21..41276e2f06 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -32,7 +32,7 @@ namespace Avalonia.Input return point; } - public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) + internal DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) : base(routedEvent) { Data = data; diff --git a/src/Avalonia.Base/Input/GotFocusEventArgs.cs b/src/Avalonia.Base/Input/GotFocusEventArgs.cs index 5cce138ee0..f3de55ebae 100644 --- a/src/Avalonia.Base/Input/GotFocusEventArgs.cs +++ b/src/Avalonia.Base/Input/GotFocusEventArgs.cs @@ -7,6 +7,11 @@ namespace Avalonia.Input /// public class GotFocusEventArgs : RoutedEventArgs { + internal GotFocusEventArgs() + { + + } + /// /// Gets or sets a value indicating how the change in focus occurred. /// diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs index b8291e9096..39c9766105 100644 --- a/src/Avalonia.Base/Input/KeyEventArgs.cs +++ b/src/Avalonia.Base/Input/KeyEventArgs.cs @@ -5,6 +5,11 @@ namespace Avalonia.Input { public class KeyEventArgs : RoutedEventArgs { + internal KeyEventArgs() + { + + } + public IKeyboardDevice? Device { get; set; } public Key Key { get; set; } diff --git a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs index b3085a038d..d5577d77af 100644 --- a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { public Vector Delta { get; set; } - public PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source, + internal PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(routedEvent, source, pointer, rootVisual, rootVisualPosition, diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 1f3c726e7b..25c10413ad 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input private readonly PointerPointProperties _properties; private readonly Lazy?>? _previousPoints; - public PointerEventArgs(RoutedEvent routedEvent, + internal PointerEventArgs(RoutedEvent routedEvent, IInteractive? source, IPointer pointer, IVisual? rootVisual, Point rootVisualPosition, @@ -30,8 +30,8 @@ namespace Avalonia.Input Timestamp = timestamp; KeyModifiers = modifiers; } - - public PointerEventArgs(RoutedEvent routedEvent, + + internal PointerEventArgs(RoutedEvent routedEvent, IInteractive? source, IPointer pointer, IVisual? rootVisual, Point rootVisualPosition, @@ -123,7 +123,7 @@ namespace Avalonia.Input public class PointerPressedEventArgs : PointerEventArgs { - public PointerPressedEventArgs( + internal PointerPressedEventArgs( IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, @@ -142,7 +142,7 @@ namespace Avalonia.Input public class PointerReleasedEventArgs : PointerEventArgs { - public PointerReleasedEventArgs( + internal PointerReleasedEventArgs( IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, @@ -163,7 +163,7 @@ namespace Avalonia.Input { public IPointer Pointer { get; } - public PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) + internal PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) { Pointer = pointer; Source = source; diff --git a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs index e5701dcf23..dbc06ec934 100644 --- a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { public Vector Delta { get; set; } - public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + internal PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, diff --git a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs index a682e8f0a4..fd1d0f42c3 100644 --- a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs +++ b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs @@ -9,8 +9,8 @@ namespace Avalonia.Input private static int _nextId = 1; public static int GetNextFreeId() => _nextId++; - - public ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent) + + internal ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent) { Id = id; Delta = delta; @@ -21,7 +21,7 @@ namespace Avalonia.Input { public int Id { get; } - public ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent) + internal ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent) { Id = id; } diff --git a/src/Avalonia.Base/Input/TappedEventArgs.cs b/src/Avalonia.Base/Input/TappedEventArgs.cs index daaab70632..8af6164fc1 100644 --- a/src/Avalonia.Base/Input/TappedEventArgs.cs +++ b/src/Avalonia.Base/Input/TappedEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { private readonly PointerEventArgs lastPointerEventArgs; - public TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs) + internal TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs) : base(routedEvent) { this.lastPointerEventArgs = lastPointerEventArgs; diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index cda0103749..787bf1abd3 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs @@ -4,6 +4,10 @@ namespace Avalonia.Input { public class TextInputEventArgs : RoutedEventArgs { + internal TextInputEventArgs() + { + + } public IKeyboardDevice? Device { get; set; } public string? Text { get; set; } diff --git a/src/Avalonia.Base/Input/VectorEventArgs.cs b/src/Avalonia.Base/Input/VectorEventArgs.cs index 000fd52f69..3e8098f904 100644 --- a/src/Avalonia.Base/Input/VectorEventArgs.cs +++ b/src/Avalonia.Base/Input/VectorEventArgs.cs @@ -5,6 +5,11 @@ namespace Avalonia.Input { public class VectorEventArgs : RoutedEventArgs { + internal VectorEventArgs() + { + + } + public Vector Vector { get; set; } } } diff --git a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs index 1cdc775b13..749d2ecc2b 100644 --- a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs +++ b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Layout /// public class EffectiveViewportChangedEventArgs : EventArgs { - public EffectiveViewportChangedEventArgs(Rect effectiveViewport) + internal EffectiveViewportChangedEventArgs(Rect effectiveViewport) { EffectiveViewport = effectiveViewport; } diff --git a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs index cac4d1693a..73840376fe 100644 --- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs +++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs @@ -12,7 +12,7 @@ namespace Avalonia.Rendering /// /// The render root that has been updated. /// The updated area. - public SceneInvalidatedEventArgs( + internal SceneInvalidatedEventArgs( IRenderRoot root, Rect dirtyRect) { From 50cea008d789f1130a9c694cf1bcbc30a8f8e8b9 Mon Sep 17 00:00:00 2001 From: daniilpavliuchyk Date: Mon, 3 Oct 2022 18:18:00 +0300 Subject: [PATCH 009/122] WIP --- native/Avalonia.Native/src/OSX/main.mm | 31 +++++++++++++++---- samples/ControlCatalog.NetCore/Program.cs | 8 +++++ src/Avalonia.Native/AvaloniaNativePlatform.cs | 1 + .../AvaloniaNativePlatformExtensions.cs | 2 ++ src/Avalonia.Native/avn.idl | 1 + 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 6ee86b21ae..cf6503b61c 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -3,7 +3,7 @@ #include "common.h" static NSString* s_appTitle = @"Avalonia"; - +static int disableSetProcessName; // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -101,7 +101,9 @@ void SetProcessName(NSString* appTitle) { class MacOptions : public ComSingleObject { + public: + FORWARD_IUNKNOWN() virtual HRESULT SetApplicationTitle(char* utf8String) override @@ -111,11 +113,17 @@ public: @autoreleasepool { auto appTitle = [NSString stringWithUTF8String: utf8String]; - - [[NSProcessInfo processInfo] setProcessName:appTitle]; - - - SetProcessName(appTitle); + if (disableSetProcessName == 0) + { + [[NSProcessInfo processInfo] setProcessName:appTitle]; + + SetProcessName(appTitle); + } + if (disableSetProcessName == 1) + { + auto rootMenu = [NSApp mainMenu]; + [rootMenu setTitle:appTitle]; + } return S_OK; } @@ -133,6 +141,17 @@ public: } } + virtual HRESULT SetDisableSetProcessName(int disable) override + { + START_COM_CALL; + + @autoreleasepool + { + disableSetProcessName = disable; + return S_OK; + } + } + }; /// See "Using POSIX Threads in a Cocoa Application" section here: diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index b1bacc6483..d0ea7ac85e 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -115,6 +115,14 @@ namespace ControlCatalog.NetCore UseDBusMenu = true, EnableIme = true }) + .With(new MacOSPlatformOptions() + { + DisableSetProcessName = true + }) + .With(new AvaloniaNativePlatformOptions() + { + AvaloniaNativeLibraryPath = "/Users/daniilpavliuchyk/Library/Developer/Xcode/DerivedData/Avalonia.Native.OSX-hchxgrlrewlcvufcdcugzgajjpdt/Build/Products/Debug/libAvalonia.Native.OSX.dylib" + }) .UseSkia() .AfterSetup(builder => { diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index b45fe5559b..acb538268f 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -102,6 +102,7 @@ namespace Avalonia.Native var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions(); _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0); + _factory.MacOptions.SetDisableSetProcessName(macOpts.DisableSetProcessName ? 1 : 0); } AvaloniaLocator.CurrentMutable diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 61889aa9e4..baa2018183 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -83,5 +83,7 @@ namespace Avalonia /// Gets or sets a value indicating whether the native macOS menu bar will be enabled for the application. /// public bool DisableNativeMenus { get; set; } + + public bool DisableSetProcessName { get; set; } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index a98d213887..b94b8ec0f4 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -599,6 +599,7 @@ interface IAvnMacOptions : IUnknown { HRESULT SetShowInDock(int show); HRESULT SetApplicationTitle(char* utf8string); + HRESULT SetDisableSetProcessName(int disable); } [uuid(04c1b049-1f43-418a-9159-cae627ec1367)] From f6cae3af060b0064a98bfdbf6df78732f2b37fcb Mon Sep 17 00:00:00 2001 From: daniilpavliuchyk Date: Mon, 3 Oct 2022 18:21:34 +0300 Subject: [PATCH 010/122] WIP --- native/Avalonia.Native/src/OSX/main.mm | 3 +-- samples/ControlCatalog.NetCore/Program.cs | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index cf6503b61c..ba8a4d439c 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -4,6 +4,7 @@ static NSString* s_appTitle = @"Avalonia"; static int disableSetProcessName; + // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -101,9 +102,7 @@ void SetProcessName(NSString* appTitle) { class MacOptions : public ComSingleObject { - public: - FORWARD_IUNKNOWN() virtual HRESULT SetApplicationTitle(char* utf8String) override diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d0ea7ac85e..b1bacc6483 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -115,14 +115,6 @@ namespace ControlCatalog.NetCore UseDBusMenu = true, EnableIme = true }) - .With(new MacOSPlatformOptions() - { - DisableSetProcessName = true - }) - .With(new AvaloniaNativePlatformOptions() - { - AvaloniaNativeLibraryPath = "/Users/daniilpavliuchyk/Library/Developer/Xcode/DerivedData/Avalonia.Native.OSX-hchxgrlrewlcvufcdcugzgajjpdt/Build/Products/Debug/libAvalonia.Native.OSX.dylib" - }) .UseSkia() .AfterSetup(builder => { From 677e89edd0b516b6f3a3f6bf625557622be216d5 Mon Sep 17 00:00:00 2001 From: daniilpavliuchyk Date: Mon, 3 Oct 2022 18:30:01 +0300 Subject: [PATCH 011/122] WIP --- native/Avalonia.Native/src/OSX/main.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index ba8a4d439c..2fc72afb46 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -3,7 +3,7 @@ #include "common.h" static NSString* s_appTitle = @"Avalonia"; -static int disableSetProcessName; +static int disableSetProcessName = 0; // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be From 73e4985746b7794fea07599a949478b71ce62a58 Mon Sep 17 00:00:00 2001 From: Fabian Huegle Date: Wed, 5 Oct 2022 19:43:20 +0200 Subject: [PATCH 012/122] Added new optional parameter "quality" for IBitmap and IBitmapImpl.Save method --- src/Avalonia.Base/Media/Imaging/Bitmap.cs | 18 +++++++--- src/Avalonia.Base/Media/Imaging/IBitmap.cs | 14 ++++++-- src/Avalonia.Base/Platform/IBitmapImpl.cs | 14 ++++++-- .../HeadlessPlatformRenderInterface.cs | 4 +-- .../Gpu/OpenGl/OpenGlBitmapImpl.cs | 4 +-- .../Helpers/ImageSavingHelper.cs | 33 +++++++++++++++---- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 4 +-- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 8 ++--- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 8 ++--- .../Media/Imaging/BitmapImpl.cs | 6 ++-- .../Media/Imaging/D2DBitmapImpl.cs | 2 +- .../Imaging/D2DRenderTargetBitmapImpl.cs | 2 +- .../Media/Imaging/WicBitmapImpl.cs | 2 +- 13 files changed, 84 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index cf8a31c3e9..ce38fc5abc 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -121,18 +121,28 @@ namespace Avalonia.Media.Imaging /// Saves the bitmap to a file. /// /// The filename. - public void Save(string fileName) + /// + /// The optional quality for compression. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting is applied. + /// + public void Save(string fileName, int? quality = null) { - PlatformImpl.Item.Save(fileName); + PlatformImpl.Item.Save(fileName, quality); } /// /// Saves the bitmap to a stream. /// /// The stream. - public void Save(Stream stream) + /// + /// The optional quality for compression. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting is applied. + /// + public void Save(Stream stream, int? quality = null) { - PlatformImpl.Item.Save(stream); + PlatformImpl.Item.Save(stream, quality); } /// diff --git a/src/Avalonia.Base/Media/Imaging/IBitmap.cs b/src/Avalonia.Base/Media/Imaging/IBitmap.cs index bd04d5ce86..e7d1862aa2 100644 --- a/src/Avalonia.Base/Media/Imaging/IBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/IBitmap.cs @@ -35,12 +35,22 @@ namespace Avalonia.Media.Imaging /// Saves the bitmap to a file. /// /// The filename. - void Save(string fileName); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(string fileName, int? quality = null); /// /// Saves the bitmap to a stream in png format. /// /// The stream. - void Save(Stream stream); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(Stream stream, int? quality = null); } } diff --git a/src/Avalonia.Base/Platform/IBitmapImpl.cs b/src/Avalonia.Base/Platform/IBitmapImpl.cs index 8f11f68e7c..299a758961 100644 --- a/src/Avalonia.Base/Platform/IBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IBitmapImpl.cs @@ -29,12 +29,22 @@ namespace Avalonia.Platform /// Saves the bitmap to a file. /// /// The filename. - void Save(string fileName); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(string fileName, int? quality = null); /// /// Saves the bitmap to a stream in png format. /// /// The stream. - void Save(Stream stream); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(Stream stream, int? quality = null); } } diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb23c6c336..999c616ba0 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -323,12 +323,12 @@ namespace Avalonia.Headless public Vector Dpi { get; } public PixelSize PixelSize { get; } public int Version { get; set; } - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { } - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index d700d4848e..857433f95f 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -25,9 +25,9 @@ namespace Avalonia.Skia public Vector Dpi { get; } public PixelSize PixelSize { get; } public int Version { get; private set; } - public void Save(string fileName) => throw new NotSupportedException(); + public void Save(string fileName, int? quality = null) => throw new NotSupportedException(); - public void Save(Stream stream) => throw new NotSupportedException(); + public void Save(Stream stream, int? quality = null) => throw new NotSupportedException(); public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) { diff --git a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs index 5fa961cc99..b4dd754822 100644 --- a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs @@ -14,14 +14,19 @@ namespace Avalonia.Skia.Helpers /// /// Image to save /// Target file. - public static void SaveImage(SKImage image, string fileName) + /// + /// The optional quality for PNG compression. + /// The quality value is interpreted from 0 - 100. If quality is null + /// the encoder applies the default quality value. + /// + public static void SaveImage(SKImage image, string fileName, int? quality = null) { if (image == null) throw new ArgumentNullException(nameof(image)); if (fileName == null) throw new ArgumentNullException(nameof(fileName)); using (var stream = File.Create(fileName)) { - SaveImage(image, stream); + SaveImage(image, stream, quality); } } @@ -29,16 +34,30 @@ namespace Avalonia.Skia.Helpers /// Save Skia image to a stream. /// /// Image to save - /// Target stream. - public static void SaveImage(SKImage image, Stream stream) + /// + /// The optional quality for PNG compression. + /// The quality value is interpreted from 0 - 100. If quality is null + /// the encoder applies the default quality value. + /// + public static void SaveImage(SKImage image, Stream stream, int? quality = null) { if (image == null) throw new ArgumentNullException(nameof(image)); if (stream == null) throw new ArgumentNullException(nameof(stream)); - using (var data = image.Encode()) + if (quality == null) { - data.SaveTo(stream); + using (var data = image.Encode()) + { + data.SaveTo(stream); + } + } + else + { + using (var data = image.Encode(SKEncodedImageFormat.Png, (int)quality)) + { + data.SaveTo(stream); + } } } } -} \ No newline at end of file +} diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 6400d67fde..e24d805050 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -139,13 +139,13 @@ namespace Avalonia.Skia } /// - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { ImageSavingHelper.SaveImage(_image, fileName); } /// - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { ImageSavingHelper.SaveImage(_image, stream); } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 01b7449b64..d3231c92a5 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -116,20 +116,20 @@ namespace Avalonia.Skia public int Version { get; private set; } = 1; /// - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { using (var image = SnapshotImage()) { - ImageSavingHelper.SaveImage(image, fileName); + ImageSavingHelper.SaveImage(image, fileName, quality); } } /// - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { using (var image = SnapshotImage()) { - ImageSavingHelper.SaveImage(image, stream); + ImageSavingHelper.SaveImage(image, stream, quality); } } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 80bfcc5973..d437f514bb 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -137,20 +137,20 @@ namespace Avalonia.Skia } /// - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { using (var image = GetSnapshot()) { - ImageSavingHelper.SaveImage(image, stream); + ImageSavingHelper.SaveImage(image, stream, quality); } } /// - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { using (var image = GetSnapshot()) { - ImageSavingHelper.SaveImage(image, fileName); + ImageSavingHelper.SaveImage(image, fileName, quality); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 843efe2cc4..059105c112 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Direct2D1.Media public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { if (Path.GetExtension(fileName) != ".png") { @@ -25,11 +25,11 @@ namespace Avalonia.Direct2D1.Media using (FileStream s = new FileStream(fileName, FileMode.Create)) { - Save(s); + Save(s, quality); } } - public abstract void Save(Stream stream); + public abstract void Save(Stream stream, int? quality = null); public virtual void Dispose() { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 2656ab4c58..a321b225a0 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -43,7 +43,7 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(_direct2DBitmap, false); } - public override void Save(Stream stream) + public override void Save(Stream stream, int? quality = null) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frame = new BitmapFrameEncode(encoder)) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 357e472d34..c5f8e837ce 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -56,7 +56,7 @@ namespace Avalonia.Direct2D1.Media.Imaging return new OptionalDispose(_renderTarget.Bitmap, false); } - public override void Save(Stream stream) + public override void Save(Stream stream, int? quality = null) { using (var wic = new WicRenderTargetBitmapImpl(PixelSize, Dpi)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 1156246b29..051790ef03 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -186,7 +186,7 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } - public override void Save(Stream stream) + public override void Save(Stream stream, int? quality = null) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frame = new BitmapFrameEncode(encoder)) From fe3b48054db194b97728f89d3ab48263bd54b818 Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Fri, 7 Oct 2022 20:01:48 +0200 Subject: [PATCH 013/122] Added DrmOutputOptions.VideMode property --- .../Avalonia.LinuxFramebuffer/DrmOutputOptions.cs | 2 ++ .../Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index e92ad02c7a..965d382238 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -23,5 +23,7 @@ namespace Avalonia.LinuxFramebuffer /// Default: R0 G0 B0 A0 /// public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); + + public PixelSize? VideoMode { get; set; } } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index ce210019c0..9bc9d8c7e6 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -51,7 +51,16 @@ namespace Avalonia.LinuxFramebuffer.Output if(connector == null) throw new InvalidOperationException("Unable to find connected DRM connector"); - var mode = connector.Modes.OrderByDescending(x => x.IsPreferred) + DrmModeInfo? mode = null; + + if (options?.VideoMode != null) + { + mode = connector.Modes + .FirstOrDefault(x => x.Resolution.Width == options.VideoMode.Value.Width && + x.Resolution.Height == options.VideoMode.Value.Height); + } + + mode ??= connector.Modes.OrderByDescending(x => x.IsPreferred) .ThenByDescending(x => x.Resolution.Width * x.Resolution.Height) //.OrderByDescending(x => x.Resolution.Width * x.Resolution.Height) .FirstOrDefault(); From fa754f21c8ef257d756b9254bbb4fc43a5d01389 Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Fri, 7 Oct 2022 20:07:22 +0200 Subject: [PATCH 014/122] Added doc --- src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index 965d382238..ce843952e7 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -24,6 +24,10 @@ namespace Avalonia.LinuxFramebuffer /// public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); + /// + /// specific the video mode with which the DrmOutput should be created, if it is not found it will fallback to the preferred mode. + /// If NULL preferred mode will be used. + /// public PixelSize? VideoMode { get; set; } } } From 30fd91121e638e0a835a305993793b4e709db51c Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:43:02 -0400 Subject: [PATCH 015/122] Comment and update the Screen class --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Platform/Screen.cs | 25 ++++++++++++++++++++-- src/Avalonia.Controls/Screens.cs | 16 +++++++++++++- src/Avalonia.X11/X11Screens.cs | 8 +++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 5 files changed, 44 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 823f59e030..6a4ed5a890 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -65,7 +65,7 @@ namespace ControlCatalog.Pages formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); - formattedText = CreateFormattedText($"Primary: {screen.Primary}"); + formattedText = CreateFormattedText($"Primary: {screen.IsPrimary}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); formattedText = diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index 976faed3fd..0f287fd5da 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -1,21 +1,42 @@ namespace Avalonia.Platform { + /// + /// Represents a single display screen. + /// public class Screen { + /// + /// Gets the pixel density of the screen. + /// This is a scaling factor so multiply by 100 to get a percentage. + /// + /// + /// Both X and Y density are assumed uniform. + /// public double PixelDensity { get; } + /// + /// Gets the overall pixel-size of the screen. + /// This generally is the raw pixel counts in both the X and Y direction. + /// public PixelRect Bounds { get; } + /// + /// Gets the actual working-area pixel-size of the screen. + /// This may be smaller to account for notches and other block-out areas. + /// public PixelRect WorkingArea { get; } - public bool Primary { get; } + /// + /// Gets a value indicating whether the screen is the primary one. + /// + public bool IsPrimary { get; } public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary) { this.PixelDensity = pixelDensity; this.Bounds = bounds; this.WorkingArea = workingArea; - this.Primary = primary; + this.IsPrimary = primary; } } } diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index a554f82f61..da37959402 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -8,13 +8,27 @@ using Avalonia.VisualTree; namespace Avalonia.Controls { + /// + /// Represents all screens available on a device. + /// public class Screens { private readonly IScreenImpl _iScreenImpl; + /// + /// Gets the total number of screens available on this device. + /// public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0; + + /// + /// Gets the list of all screens available on this device. + /// public IReadOnlyList All => _iScreenImpl?.AllScreens ?? Array.Empty(); - public Screen? Primary => All.FirstOrDefault(x => x.Primary); + + /// + /// Gets the primary screen on this device. + /// + public Screen? Primary => All.FirstOrDefault(x => x.IsPrimary); public Screens(IScreenImpl iScreenImpl) { diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index bcaafb6a53..a65f09ee63 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -9,7 +9,7 @@ using JetBrains.Annotations; namespace Avalonia.X11 { - class X11Screens : IScreenImpl + class X11Screens : IScreenImpl { private IX11Screens _impl; @@ -218,7 +218,7 @@ namespace Avalonia.X11 public int ScreenCount => _impl.Screens.Length; public IReadOnlyList AllScreens => - _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.Primary)).ToArray(); + _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray(); } interface IX11Screens @@ -281,7 +281,7 @@ namespace Avalonia.X11 { private const int FullHDWidth = 1920; private const int FullHDHeight = 1080; - public bool Primary { get; } + public bool IsPrimary { get; } public string Name { get; set; } public PixelRect Bounds { get; set; } public Size? PhysicalSize { get; set; } @@ -291,7 +291,7 @@ namespace Avalonia.X11 public X11Screen(PixelRect bounds, bool primary, string name, Size? physicalSize, double? pixelDensity) { - Primary = primary; + IsPrimary = primary; Name = name; Bounds = bounds; if (physicalSize == null && pixelDensity == null) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0f243fcf9f..9741f3f804 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -224,7 +224,7 @@ namespace Avalonia.Win32 } } - private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.Primary)?.PixelDensity ?? 1; + private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.PixelDensity ?? 1; public double RenderScaling => _scaling; From 31a15075ed6947e6f7998279d6daf59438304728 Mon Sep 17 00:00:00 2001 From: robloo Date: Sat, 8 Oct 2022 14:47:26 -0400 Subject: [PATCH 016/122] Rename missed parameter --- src/Avalonia.Controls/Platform/Screen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index 0f287fd5da..cdab81705f 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -31,12 +31,12 @@ /// public bool IsPrimary { get; } - public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary) + public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool isPrimary) { this.PixelDensity = pixelDensity; this.Bounds = bounds; this.WorkingArea = workingArea; - this.IsPrimary = primary; + this.IsPrimary = isPrimary; } } } From 7c8982ae3f7db992f8482010e7c87b0974f75d44 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 10 Oct 2022 11:55:56 +0000 Subject: [PATCH 017/122] prevent selection from being updated on focus when toggle is enabled --- src/Avalonia.Controls/ListBox.cs | 3 +- .../Primitives/SelectingItemsControl.cs | 45 +++++++++++++------ 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 80b5259a53..86118d7b00 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -139,7 +139,8 @@ namespace Avalonia.Controls e.Source, true, e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), - e.KeyModifiers.HasAllFlags(KeyModifiers.Control)); + e.KeyModifiers.HasAllFlags(KeyModifiers.Control), + fromFocus: true); } } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index ea20247b4b..e03b02a479 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -586,6 +586,14 @@ namespace Avalonia.Controls.Primitives Selection.SelectAll(); e.Handled = true; } + else if (e.Key == Key.Space || e.Key == Key.Enter) + { + e.Handled = UpdateSelectionFromEventSource( + e.Source, + true, + e.KeyModifiers.HasFlag(KeyModifiers.Shift), + e.KeyModifiers.HasFlag(KeyModifiers.Control)); + } } } @@ -662,12 +670,14 @@ namespace Avalonia.Controls.Primitives /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). /// Whether the event is a right-click. + /// Wheter the event is a focus event protected void UpdateSelection( int index, bool select = true, bool rangeModifier = false, bool toggleModifier = false, - bool rightButton = false) + bool rightButton = false, + bool fromFocus = false) { if (index < 0 || index >= ItemCount) { @@ -696,22 +706,25 @@ namespace Avalonia.Controls.Primitives Selection.Clear(); Selection.SelectRange(Selection.AnchorIndex, index); } - else if (multi && toggle) + else if (!fromFocus && toggle) { - if (Selection.IsSelected(index) == true) + if (multi) { - Selection.Deselect(index); + if (Selection.IsSelected(index) == true) + { + Selection.Deselect(index); + } + else + { + Selection.Select(index); + } } else { - Selection.Select(index); + SelectedIndex = (SelectedIndex == index) ? -1 : index; } } - else if (toggle) - { - SelectedIndex = (SelectedIndex == index) ? -1 : index; - } - else + else if (!toggle) { using var operation = Selection.BatchUpdate(); Selection.Clear(); @@ -735,18 +748,20 @@ namespace Avalonia.Controls.Primitives /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). /// Whether the event is a right-click. + /// Wheter the event is a focus event protected void UpdateSelection( IControl container, bool select = true, bool rangeModifier = false, bool toggleModifier = false, - bool rightButton = false) + bool rightButton = false, + bool fromFocus = false) { var index = ItemContainerGenerator?.IndexFromContainer(container) ?? -1; if (index != -1) { - UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton); + UpdateSelection(index, select, rangeModifier, toggleModifier, rightButton, fromFocus); } } @@ -759,6 +774,7 @@ namespace Avalonia.Controls.Primitives /// Whether the range modifier is enabled (i.e. shift key). /// Whether the toggle modifier is enabled (i.e. ctrl key). /// Whether the event is a right-click. + /// Wheter the event is a focus event /// /// True if the event originated from a container that belongs to the control; otherwise /// false. @@ -768,13 +784,14 @@ namespace Avalonia.Controls.Primitives bool select = true, bool rangeModifier = false, bool toggleModifier = false, - bool rightButton = false) + bool rightButton = false, + bool fromFocus = false) { var container = GetContainerFromEventSource(eventSource); if (container != null) { - UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton); + UpdateSelection(container, select, rangeModifier, toggleModifier, rightButton, fromFocus); return true; } From 89f031574ba9ca9cdc231242a87c4b852e4070b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 10 Oct 2022 13:19:25 +0000 Subject: [PATCH 018/122] Add tests --- .../ListBoxTests_Multiple.cs | 8 +-- .../ListBoxTests_Single.cs | 52 +++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs index 7c7cdd08db..556959effb 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Add_To_Selection() + public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Not_Add_To_Selection() { var target = new ListBox { @@ -56,11 +56,11 @@ namespace Avalonia.Controls.UnitTests KeyModifiers = KeyModifiers.Control }); - Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo" }, target.SelectedItems); } [Fact] - public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Remove_From_Selection() + public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Not_Remove_From_Selection() { var target = new ListBox { @@ -81,7 +81,7 @@ namespace Avalonia.Controls.UnitTests KeyModifiers = KeyModifiers.Control }); - Assert.Equal(new[] { "Bar" }, target.SelectedItems); + Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems); } private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope) diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs index bf516748cc..726a39ac46 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs @@ -59,6 +59,58 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(0, target.SelectedIndex); } + [Fact] + public void Focusing_Item_With_Arrow_Key_And_Ctrl_Pressed_Should_Not_Select_It() + { + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + + ApplyTemplate(target); + + target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs + { + RoutedEvent = InputElement.GotFocusEvent, + NavigationMethod = NavigationMethod.Directional, + KeyModifiers = KeyModifiers.Control + }); + + Assert.Equal(-1, target.SelectedIndex); + } + + [Fact] + public void Pressing_Space_On_Focused_Item_With_Ctrl_Pressed_Should_Select_It() + { + using (UnitTestApplication.Start()) + { + var target = new ListBox + { + Template = new FuncControlTemplate(CreateListBoxTemplate), + Items = new[] { "Foo", "Bar", "Baz " }, + }; + AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object); + ApplyTemplate(target); + + target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs + { + RoutedEvent = InputElement.GotFocusEvent, + NavigationMethod = NavigationMethod.Directional, + KeyModifiers = KeyModifiers.Control + }); + + target.Presenter.Panel.Children[0].RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = Key.Space, + KeyModifiers = KeyModifiers.Control + }); + + Assert.Equal(0, target.SelectedIndex); + } + } + [Fact] public void Clicking_Item_Should_Select_It() { From 5f9b193b311f030df6463c1188adf4e698239655 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 12 Oct 2022 22:04:13 +0100 Subject: [PATCH 019/122] fix build on net7 sdks with scoped keyword. --- src/Avalonia.Base/Utilities/IdentifierParser.cs | 2 +- .../Markup/Parsers/BindingExpressionGrammar.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs index b105d0746b..ee176a6b85 100644 --- a/src/Avalonia.Base/Utilities/IdentifierParser.cs +++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs @@ -8,7 +8,7 @@ namespace Avalonia.Utilities #endif static class IdentifierParser { - public static ReadOnlySpan ParseIdentifier(this ref CharacterReader r) + public static ReadOnlySpan ParseIdentifier(this scoped ref CharacterReader r) { if (IsValidIdentifierStart(r.Peek)) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 439bc15243..0a9fbcfacb 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -168,7 +168,7 @@ namespace Avalonia.Markup.Parsers } } - private static State ParseAttachedProperty(ref CharacterReader r, List nodes) + private static State ParseAttachedProperty(scoped ref CharacterReader r, List nodes) { var (ns, owner) = ParseTypeName(ref r); @@ -318,7 +318,7 @@ namespace Avalonia.Markup.Parsers return State.AfterMember; } - private static TypeName ParseTypeName(ref CharacterReader r) + private static TypeName ParseTypeName(scoped ref CharacterReader r) { ReadOnlySpan ns, typeName; ns = ReadOnlySpan.Empty; From d1e58bfde540935691c7515138982fed7ede3269 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 12 Oct 2022 22:08:18 +0100 Subject: [PATCH 020/122] use net7 rc2 sdk. --- azure-pipelines.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 33b2dc670a..903f9e3843 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.100-rc.1.22431.12' + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.1.22431.12 + version: 7.0.100-rc.2.22477.23 - 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-rc.1.22431.12' + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.1.22431.12 + version: 7.0.100-rc.2.22477.23 - 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-rc.1.22431.12' + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.1.22431.12 + version: 7.0.100-rc.2.22477.23 - task: CmdLine@2 displayName: 'Install Workloads' From f91a788d18a40c187cd09e3516d86619869429c4 Mon Sep 17 00:00:00 2001 From: dif-sam <41672086+dif-sam@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:21:54 +0400 Subject: [PATCH 021/122] Suggested fix for #9173 trying to deffere request for BringIntoView, when TreeViewItem not ready yet. --- src/Avalonia.Controls/TreeViewItem.cs | 29 +++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index ada081b808..c2304abbc8 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -45,6 +45,8 @@ namespace Avalonia.Controls private IControl? _header; private bool _isExpanded; private int _level; + private bool _templateApplied; + private bool _deferredBringIntoViewFlag; /// /// Initializes static members of the class. @@ -136,15 +138,24 @@ namespace Avalonia.Controls protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e) { - if (e.TargetObject == this && _header != null) + if (e.TargetObject == this) { - var m = _header.TransformToVisual(this); + if (!_templateApplied) + { + _deferredBringIntoViewFlag = true; + return; + } - if (m.HasValue) + if (_header != null) { - var bounds = new Rect(_header.Bounds.Size); - var rect = bounds.TransformToAABB(m.Value); - e.TargetRect = rect; + var m = _header.TransformToVisual(this); + + if (m.HasValue) + { + var bounds = new Rect(_header.Bounds.Size); + var rect = bounds.TransformToAABB(m.Value); + e.TargetRect = rect; + } } } } @@ -187,6 +198,12 @@ namespace Avalonia.Controls protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _header = e.NameScope.Find("PART_Header"); + _templateApplied = true; + if (_deferredBringIntoViewFlag) + { + _deferredBringIntoViewFlag = false; + Dispatcher.UIThread.Post(this.BringIntoView); // must use the Dispatcher, otherwise the TreeView doesn't scroll + } } private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T : class From 65d39603fac9f963324158ab8d7c420565c5785b Mon Sep 17 00:00:00 2001 From: dif-sam <41672086+dif-sam@users.noreply.github.com> Date: Thu, 13 Oct 2022 16:55:55 +0400 Subject: [PATCH 022/122] Forgotten namespase added to usings --- src/Avalonia.Controls/TreeViewItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index c2304abbc8..2f96e6911f 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.LogicalTree; +using Avalonia.Threading; namespace Avalonia.Controls { From 94afe971d9af03b8f5f7c36888b376c4476631dd Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 14 Oct 2022 15:13:12 +0200 Subject: [PATCH 023/122] feat: Enable Rule CA1820 --- .editorconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.editorconfig b/.editorconfig index 337760636b..09f0d3e6ac 100644 --- a/.editorconfig +++ b/.editorconfig @@ -141,6 +141,8 @@ dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomme dotnet_diagnostic.CA1802.severity = warning # CA1825: Avoid zero-length array allocations dotnet_diagnostic.CA1825.severity = warning +# CA1820: Test for empty strings using string length +dotnet_diagnostic.CA1820.severity = warning # Wrapping preferences csharp_wrap_before_ternary_opsigns = false From 36cfc43a596c2181278b1a86b00fb4761dca0fc8 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 14 Oct 2022 15:14:35 +0200 Subject: [PATCH 024/122] feat: Address CA1820 Rule --- src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs | 4 ++-- src/Avalonia.Controls/AppBuilderBase.cs | 4 ++-- src/Avalonia.Controls/DateTimePickers/TimePicker.cs | 2 +- src/Avalonia.Controls/DefinitionBase.cs | 2 +- .../Remote/HtmlTransport/SimpleWebSocketHttpServer.cs | 2 +- src/Avalonia.X11/X11Structs.cs | 2 +- .../Media/TextFormatting/GraphemeBreakTestDataGenerator.cs | 6 +++--- tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 2 +- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs b/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs index 82670bf989..6144679b60 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridValueConverter.cs @@ -28,8 +28,8 @@ namespace Avalonia.Controls { if (targetType != null && targetType.IsNullableType()) { - String strValue = value as String; - if (strValue == String.Empty) + var strValue = value as string; + if (string.IsNullOrEmpty(strValue)) { return null; } diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 6b7101cd49..1dfad7dcc5 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -210,9 +210,9 @@ namespace Avalonia.Controls { var moduleInitializers = from assembly in AppDomain.CurrentDomain.GetAssemblies() from attribute in assembly.GetCustomAttributes() - where attribute.ForWindowingSubsystem == "" + where string.IsNullOrEmpty(attribute.ForWindowingSubsystem) || attribute.ForWindowingSubsystem == WindowingSubsystemName - where attribute.ForRenderingSubsystem == "" + where string.IsNullOrEmpty(attribute.ForRenderingSubsystem) || attribute.ForRenderingSubsystem == RenderingSubsystemName group attribute by attribute.Name into exports select (from export in exports diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs index a709e4fb49..c3baa6f17f 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs @@ -129,7 +129,7 @@ namespace Avalonia.Controls get => _clockIdentifier; set { - if (!(string.IsNullOrEmpty(value) || value == "" || value == "12HourClock" || value == "24HourClock")) + if (!(string.IsNullOrEmpty(value) || value == "12HourClock" || value == "24HourClock")) throw new ArgumentException("Invalid ClockIdentifier"); SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value); SetGrid(); diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index eb09ff397a..64a02ccb46 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -366,7 +366,7 @@ namespace Avalonia.Controls string id = (string)value; - if (id != string.Empty) + if (!string.IsNullOrEmpty(id)) { int i = -1; while (++i < id.Length) diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs index 9a872df960..6bfae536c9 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs @@ -72,7 +72,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport while (true) { line = await ReadLineAsync(); - if (line == "") + if (string.IsNullOrEmpty(line)) break; sp = line.Split(new[] {':'}, 2); headers[sp[0]] = sp[1].TrimStart(); diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 23abd31b2c..3f0a6aeb67 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -661,7 +661,7 @@ namespace Avalonia.X11 { Type type = ev.GetType (); FieldInfo [] fields = type.GetFields (System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance); for (int i = 0; i < fields.Length; i++) { - if (result != string.Empty) { + if (!string.IsNullOrEmpty(result)) { result += ", "; } object value = fields [i].GetValue (ev); diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs index f6616d74a2..9b2cb481e0 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs @@ -68,7 +68,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting var graphemeChars = elements[0].Replace(" × ", " ").Split(' '); - var codepoints = graphemeChars.Where(x => x != "" && x != "×") + var codepoints = graphemeChars.Where(x => !string.IsNullOrEmpty(x) && x != "×") .Select(x => Convert.ToInt32(x, 16)).ToList(); var grapheme = codepoints.ToArray(); @@ -77,10 +77,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { var remainingChars = elements[1].Replace(" × ", " ").Split(' '); - var remaining = remainingChars.Where(x => x != "" && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); + var remaining = remainingChars.Where(x => !string.IsNullOrEmpty(x) && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); codepoints.AddRange(remaining); - } + } var data = new GraphemeBreakData { diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index c8bd289e54..122176fbe0 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests textbox.Text = String.Empty; Dispatcher.UIThread.RunJobs(); - Assert.True(control.SearchText == String.Empty); + Assert.True(string.IsNullOrEmpty(control.SearchText)); Assert.False(control.IsDropDownOpen); Assert.True(closeEvent); }); diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index d1fa522206..a2d0842028 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -559,7 +559,7 @@ namespace Avalonia.Controls.UnitTests Text = "0123456789" }; - Assert.True(target.SelectedText == ""); + Assert.True(string.IsNullOrEmpty(target.SelectedText)); target.SelectionStart = 2; target.SelectionEnd = 4; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 23a330c96f..f2982046e5 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -473,7 +473,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); - Assert.True(target.SelectedText == ""); + Assert.True(string.IsNullOrEmpty(target.SelectedText)); target.SelectionStart = 2; target.SelectionEnd = 4; From cc915f888cca167e32157f66a81654c8959e1374 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Sun, 16 Oct 2022 16:31:00 +0200 Subject: [PATCH 025/122] fix: Under Windows - Arithmetic operation resulted in an overflow. --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index 9d1920498b..f28f4fd740 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1877,7 +1877,7 @@ namespace Avalonia.Win32.Interop public static uint LGID(IntPtr HKL) { - return (uint)(HKL.ToInt32() & 0xffff); + return (uint)(HKL.ToInt64() & 0xffff); } public const int SORT_DEFAULT = 0; From ee0c1b47781fd0ea48ec174c00552735452e1844 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Oct 2022 14:19:24 +0000 Subject: [PATCH 026/122] Use span directly when available --- src/Avalonia.Base/Media/Color.cs | 5 +- src/Avalonia.Base/Utilities/SpanHelpers.cs | 31 ++++++++++ src/Avalonia.Build.Tasks/SpanCompat.cs | 61 +++++++++++++++++++ .../Markup/Parsers/SelectorGrammar.cs | 18 +++--- 4 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 src/Avalonia.Base/Utilities/SpanHelpers.cs diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index cb90404f6d..aee048cc99 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -9,6 +9,7 @@ using System; using System.Globalization; #if !BUILDTASK using Avalonia.Animation.Animators; +using static Avalonia.Utilities.SpanHelpers; #endif namespace Avalonia.Media @@ -295,9 +296,7 @@ namespace Avalonia.Media return false; } - // TODO: (netstandard 2.1) Can use allocation free parsing. - if (!uint.TryParse(input.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out var parsed)) + if (!input.TryParseFromHexToUInt(out var parsed)) { return false; } diff --git a/src/Avalonia.Base/Utilities/SpanHelpers.cs b/src/Avalonia.Base/Utilities/SpanHelpers.cs new file mode 100644 index 0000000000..e4fab5ac7f --- /dev/null +++ b/src/Avalonia.Base/Utilities/SpanHelpers.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; +using System.Runtime.CompilerServices; + +namespace Avalonia.Utilities +{ + public static class SpanHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseFromHexToUInt(this ReadOnlySpan span, out uint value) + { +#if NETSTANDARD2_0 + return uint.TryParse(span.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, + out value); +#else + return uint.TryParse(span, NumberStyles.HexNumber, CultureInfo.InvariantCulture, + out value); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseToInt(this ReadOnlySpan span, out int value) + { +#if NETSTANDARD2_0 + return int.TryParse(span.ToString(), out value); +#else + return int.TryParse(span, out value); +#endif + } + } +} diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index 25f8d0175a..6da1fbd07c 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -1,4 +1,7 @@ #if !NETCOREAPP3_1_OR_GREATER +using System.Globalization; +using System.Runtime.CompilerServices; + namespace System { // This is a hack to enable our span code to work inside MSBuild task without referencing System.Memory @@ -9,6 +12,8 @@ namespace System private int _length; public int Length => _length; + public static implicit operator ReadOnlySpan(string s) => new ReadOnlySpan(s); + public ReadOnlySpan(string s) : this(s, 0, s.Length) { @@ -63,8 +68,64 @@ namespace System return Slice(start); } + public ReadOnlySpan TrimEnd() + { + int end = Length - 1; + for (; end >= 0; end--) + { + if (!char.IsWhiteSpace(this[end])) + { + break; + } + } + return Slice(0, end + 1); + } + + public ReadOnlySpan Trim() + { + return TrimStart().TrimEnd(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseFromHexToUInt(out uint value) + { + return uint.TryParse(ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, + out value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseToInt(out int value) + { + return int.TryParse(ToString(), out value); + } + public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); + internal int IndexOf(string v, StringComparison ordinal, int start = 0) + { + if(Length == 0 || string.IsNullOrEmpty(v)) + { + return -1; + } + + for (var c = start; c < _length; c++) + { + if (this[c] == v[0]) + { + for(var i = 0; i < v.Length; i++) + { + if (this[c + i] != v[i]) + { + break; + } + } + return c; + } + } + + return -1; + } + public static implicit operator ReadOnlySpan(char[] arr) => new ReadOnlySpan(new string(arr)); } diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 16856e674d..a5e11cd233 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -376,28 +376,28 @@ namespace Avalonia.Markup.Parsers if (r.Peek == 'o') { - var constArg = r.TakeUntil(')').ToString().Trim(); - if (constArg.Equals("odd", StringComparison.Ordinal)) + var constArg = r.TakeUntil(')').Trim(); + if (constArg.SequenceEqual("odd".AsSpan())) { step = 2; offset = 1; } else { - throw new ExpressionParseException(r.Position, $"Expected nth-child(odd). Actual '{constArg}'."); + throw new ExpressionParseException(r.Position, $"Expected nth-child(odd). Actual '{constArg.ToString()}'."); } } else if (r.Peek == 'e') { - var constArg = r.TakeUntil(')').ToString().Trim(); - if (constArg.Equals("even", StringComparison.Ordinal)) + var constArg = r.TakeUntil(')').Trim(); + if (constArg.SequenceEqual("even".AsSpan())) { step = 2; offset = 0; } else { - throw new ExpressionParseException(r.Position, $"Expected nth-child(even). Actual '{constArg}'."); + throw new ExpressionParseException(r.Position, $"Expected nth-child(even). Actual '{constArg.ToString()}'."); } } else @@ -405,7 +405,7 @@ namespace Avalonia.Markup.Parsers r.SkipWhitespace(); var stepOrOffset = 0; - var stepOrOffsetStr = r.TakeWhile(c => char.IsDigit(c) || c == '-' || c == '+').ToString(); + var stepOrOffsetStr = r.TakeWhile(c => char.IsDigit(c) || c == '-' || c == '+'); if (stepOrOffsetStr.Length == 0 || (stepOrOffsetStr.Length == 1 && stepOrOffsetStr[0] == '+')) @@ -417,7 +417,7 @@ namespace Avalonia.Markup.Parsers { stepOrOffset = -1; } - else if (!int.TryParse(stepOrOffsetStr.ToString(), out stepOrOffset)) + else if (!stepOrOffsetStr.TryParseToInt(out stepOrOffset)) { throw new ExpressionParseException(r.Position, "Couldn't parse nth-child step or offset value. Integer was expected."); } @@ -462,7 +462,7 @@ namespace Avalonia.Markup.Parsers r.SkipWhitespace(); if (sign != 0 - && !int.TryParse(r.TakeUntil(')').ToString(), out offset)) + && !r.TakeUntil(')').TryParseToInt(out offset)) { throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected."); } From 868e5a5488e3e0d4a6954990fd61df5de08cbc2d Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Oct 2022 14:45:37 +0000 Subject: [PATCH 027/122] switch to using helper in the color classes --- src/Avalonia.Base/Media/Color.cs | 44 ++++++++-------------- src/Avalonia.Base/Media/HslColor.cs | 30 ++++++--------- src/Avalonia.Base/Media/HsvColor.cs | 30 ++++++--------- src/Avalonia.Base/Utilities/SpanHelpers.cs | 24 ++++++++++++ src/Avalonia.Build.Tasks/SpanCompat.cs | 18 ++++++++- 5 files changed, 80 insertions(+), 66 deletions(-) diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index aee048cc99..14ef357393 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -381,9 +381,9 @@ namespace Avalonia.Media if (components.Length == 3) // RGB { - if (InternalTryParseByte(components[0], out byte red) && - InternalTryParseByte(components[1], out byte green) && - InternalTryParseByte(components[2], out byte blue)) + if (InternalTryParseByte(components[0].AsSpan(), out byte red) && + InternalTryParseByte(components[1].AsSpan(), out byte green) && + InternalTryParseByte(components[2].AsSpan(), out byte blue)) { color = new Color(0xFF, red, green, blue); return true; @@ -391,10 +391,10 @@ namespace Avalonia.Media } else if (components.Length == 4) // RGBA { - if (InternalTryParseByte(components[0], out byte red) && - InternalTryParseByte(components[1], out byte green) && - InternalTryParseByte(components[2], out byte blue) && - InternalTryParseDouble(components[3], out double alpha)) + if (InternalTryParseByte(components[0].AsSpan(), out byte red) && + InternalTryParseByte(components[1].AsSpan(), out byte green) && + InternalTryParseByte(components[2].AsSpan(), out byte blue) && + InternalTryParseDouble(components[3].AsSpan(), out double alpha)) { color = new Color((byte)Math.Round(alpha * 255.0), red, green, blue); return true; @@ -402,17 +402,14 @@ namespace Avalonia.Media } // Local function to specially parse a byte value with an optional percentage sign - bool InternalTryParseByte(string inString, out byte outByte) + bool InternalTryParseByte(ReadOnlySpan inString, out byte outByte) { // The percent sign, if it exists, must be at the end of the number - int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); + int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal); if (percentIndex >= 0) { - var result = double.TryParse( - inString.Substring(0, percentIndex), - NumberStyles.Number, - CultureInfo.InvariantCulture, + var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( out double percentage); outByte = (byte)Math.Round((percentage / 100.0) * 255.0); @@ -420,37 +417,28 @@ namespace Avalonia.Media } else { - return byte.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToByte( out outByte); } } // Local function to specially parse a double value with an optional percentage sign - bool InternalTryParseDouble(string inString, out double outDouble) + bool InternalTryParseDouble(ReadOnlySpan inString, out double outDouble) { // The percent sign, if it exists, must be at the end of the number - int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); + int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal); if (percentIndex >= 0) { - var result = double.TryParse( - inString.Substring(0, percentIndex), - NumberStyles.Number, - CultureInfo.InvariantCulture, - out double percentage); + var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + out double percentage); outDouble = percentage / 100.0; return result; } else { - return double.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToDouble( out outDouble); } } diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 485bb1db16..5a9e5cd54b 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -302,9 +302,9 @@ namespace Avalonia.Media if (components.Length == 3) // HSL { - if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && - TryInternalParse(components[1], out double saturation) && - TryInternalParse(components[2], out double lightness)) + if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + TryInternalParse(components[1].AsSpan(), out double saturation) && + TryInternalParse(components[2].AsSpan(), out double lightness)) { hslColor = new HslColor(1.0, hue, saturation, lightness); return true; @@ -312,10 +312,10 @@ namespace Avalonia.Media } else if (components.Length == 4) // HSLA { - if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && - TryInternalParse(components[1], out double saturation) && - TryInternalParse(components[2], out double lightness) && - TryInternalParse(components[3], out double alpha)) + if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + TryInternalParse(components[1].AsSpan(), out double saturation) && + TryInternalParse(components[2].AsSpan(), out double lightness) && + TryInternalParse(components[3].AsSpan(), out double alpha)) { hslColor = new HslColor(alpha, hue, saturation, lightness); return true; @@ -323,28 +323,22 @@ namespace Avalonia.Media } // Local function to specially parse a double value with an optional percentage sign - bool TryInternalParse(string inString, out double outDouble) + bool TryInternalParse(ReadOnlySpan inString, out double outDouble) { // The percent sign, if it exists, must be at the end of the number - int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); + int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal); if (percentIndex >= 0) { - var result = double.TryParse( - inString.Substring(0, percentIndex), - NumberStyles.Number, - CultureInfo.InvariantCulture, - out double percentage); + var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + out double percentage); outDouble = percentage / 100.0; return result; } else { - return double.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToDouble( out outDouble); } } diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 512e57ae07..30a6c7ef4e 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -302,9 +302,9 @@ namespace Avalonia.Media if (components.Length == 3) // HSV { - if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && - TryInternalParse(components[1], out double saturation) && - TryInternalParse(components[2], out double value)) + if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + TryInternalParse(components[1].AsSpan(), out double saturation) && + TryInternalParse(components[2].AsSpan(), out double value)) { hsvColor = new HsvColor(1.0, hue, saturation, value); return true; @@ -312,10 +312,10 @@ namespace Avalonia.Media } else if (components.Length == 4) // HSVA { - if (double.TryParse(components[0], NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && - TryInternalParse(components[1], out double saturation) && - TryInternalParse(components[2], out double value) && - TryInternalParse(components[3], out double alpha)) + if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + TryInternalParse(components[1].AsSpan(), out double saturation) && + TryInternalParse(components[2].AsSpan(), out double value) && + TryInternalParse(components[3].AsSpan(), out double alpha)) { hsvColor = new HsvColor(alpha, hue, saturation, value); return true; @@ -323,28 +323,22 @@ namespace Avalonia.Media } // Local function to specially parse a double value with an optional percentage sign - bool TryInternalParse(string inString, out double outDouble) + bool TryInternalParse(ReadOnlySpan inString, out double outDouble) { // The percent sign, if it exists, must be at the end of the number - int percentIndex = inString.IndexOf("%", StringComparison.Ordinal); + int percentIndex = inString.IndexOf("%".AsSpan(), StringComparison.Ordinal); if (percentIndex >= 0) { - var result = double.TryParse( - inString.Substring(0, percentIndex), - NumberStyles.Number, - CultureInfo.InvariantCulture, - out double percentage); + var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + out double percentage); outDouble = percentage / 100.0; return result; } else { - return double.TryParse( - inString, - NumberStyles.Number, - CultureInfo.InvariantCulture, + return inString.TryParseNumberToDouble( out outDouble); } } diff --git a/src/Avalonia.Base/Utilities/SpanHelpers.cs b/src/Avalonia.Base/Utilities/SpanHelpers.cs index e4fab5ac7f..6b779dfd87 100644 --- a/src/Avalonia.Base/Utilities/SpanHelpers.cs +++ b/src/Avalonia.Base/Utilities/SpanHelpers.cs @@ -25,6 +25,30 @@ namespace Avalonia.Utilities return int.TryParse(span.ToString(), out value); #else return int.TryParse(span, out value); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseNumberToDouble(this ReadOnlySpan span, out double value) + { +#if NETSTANDARD2_0 + return double.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); +#else + return double.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, + out value); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryParseNumberToByte(this ReadOnlySpan span, out byte value) + { +#if NETSTANDARD2_0 + return byte.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); +#else + return byte.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, + out value); #endif } } diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index 6da1fbd07c..af78178b5e 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -99,11 +99,25 @@ namespace System return int.TryParse(ToString(), out value); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseNumberToDouble(out double value) + { + return double.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryParseNumberToByte(out byte value) + { + return byte.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, + out value); + } + public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); - internal int IndexOf(string v, StringComparison ordinal, int start = 0) + internal int IndexOf(ReadOnlySpan v, StringComparison ordinal, int start = 0) { - if(Length == 0 || string.IsNullOrEmpty(v)) + if(Length == 0 || v.IsEmpty) { return -1; } From b031dcfe1f7c9a8b61bed198ae4ce2b926dfbe68 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 18 Oct 2022 12:44:08 +0600 Subject: [PATCH 028/122] Removed deprecated XAML compiler features --- packages/Avalonia/AvaloniaBuildTasks.targets | 3 -- .../CompileAvaloniaXamlTask.cs | 5 +-- .../GenerateAvaloniaResourcesTask.cs | 6 ---- src/Avalonia.Build.Tasks/Program.cs | 3 +- .../XamlCompilerTaskExecutor.Helpers.cs | 32 ------------------- .../XamlCompilerTaskExecutor.cs | 17 +++------- .../Avalonia.MicroCom.csproj | 3 -- 7 files changed, 7 insertions(+), 62 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index d43a5c1624..4b9e33ffe8 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -3,7 +3,6 @@ <_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild) <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false low - <_AvaloniaPatchComInterop Condition="'$(_AvaloniaPatchComInterop)' == ''">false <_AvaloniaSkipXamlCompilation Condition="'$(_AvaloniaSkipXamlCompilation)' == ''">false @@ -71,7 +70,6 @@ Output="$(AvaloniaResourcesTemporaryFilePath)" Root="$(MSBuildProjectDirectory)" Resources="@(AvaloniaResource)" - EmbeddedResources="@(EmbeddedResources)" ReportImportance="$(AvaloniaXamlReportImportance)"/> diff --git a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs index 62a9ed27be..13e8b14891 100644 --- a/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs +++ b/src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs @@ -37,8 +37,7 @@ namespace Avalonia.Build.Tasks var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input, File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(), ProjectDirectory, OutputPath, VerifyIl, outputImportance, - (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, - EnableComInteropPatching, SkipXamlCompilation, DebuggerLaunch); + (SignAssembly && !DelaySign) ? AssemblyOriginatorKeyFile : null, SkipXamlCompilation, DebuggerLaunch); if (!res.Success) return false; if (!res.WrittenFile) @@ -72,8 +71,6 @@ namespace Avalonia.Build.Tasks public string OutputPath { get; set; } public bool VerifyIl { get; set; } - - public bool EnableComInteropPatching { get; set; } public bool SkipXamlCompilation { get; set; } public string AssemblyOriginatorKeyFile { get; set; } diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index c20b2f656e..6bd1e29956 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -19,8 +19,6 @@ namespace Avalonia.Build.Tasks public string Root { get; set; } [Required] public string Output { get; set; } - [Required] - public ITaskItem[] EmbeddedResources { get; set; } public string ReportImportance { get; set; } @@ -149,10 +147,6 @@ namespace Avalonia.Build.Tasks Enum.TryParse(ReportImportance, out _reportImportance); BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance); - - foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml") || r.ItemSpec.EndsWith(".axaml"))) - BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec, - "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work"); var resources = BuildResourceSources(); if (!PreProcessXamlFiles(resources)) diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs index e3f00968fd..f42fab5964 100644 --- a/src/Avalonia.Build.Tasks/Program.cs +++ b/src/Avalonia.Build.Tasks/Program.cs @@ -44,8 +44,7 @@ namespace Avalonia.Build.Tasks OutputPath = args[2], BuildEngine = new ConsoleBuildEngine(), ProjectDirectory = Directory.GetCurrentDirectory(), - VerifyIl = true, - EnableComInteropPatching = true + VerifyIl = true }.Execute() ? 0 : 2; diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs index 5a2c74e16f..f83e07bd74 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.Helpers.cs @@ -24,38 +24,6 @@ namespace Avalonia.Build.Tasks string Name { get; } IEnumerable Resources { get; } } - - class EmbeddedResources : IResourceGroup - { - private readonly AssemblyDefinition _asm; - public string Name => "EmbeddedResource"; - - public IEnumerable Resources => _asm.MainModule.Resources.OfType() - .Select(r => new WrappedResource(_asm, r)).ToList(); - - public EmbeddedResources(AssemblyDefinition asm) - { - _asm = asm; - } - class WrappedResource : IResource - { - private readonly AssemblyDefinition _asm; - private readonly EmbeddedResource _res; - - public WrappedResource(AssemblyDefinition asm, EmbeddedResource res) - { - _asm = asm; - _res = res; - } - - public string Uri => $"resm:{Name}?assembly={_asm.Name.Name}"; - public string Name => _res.Name; - public string FilePath => Name; - public byte[] FileContents => _res.GetResourceData(); - - public void Remove() => _asm.MainModule.Resources.Remove(_res); - } - } class AvaloniaResources : IResourceGroup { diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 5915388822..d2ccea8fa4 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -39,15 +39,15 @@ namespace Avalonia.Build.Tasks public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, - string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, + string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation) { - return Compile(engine, input, references, projectDirectory, output, verifyIl, logImportance, strongNameKey, patchCom, skipXamlCompilation, debuggerLaunch:false); + return Compile(engine, input, references, projectDirectory, output, verifyIl, logImportance, strongNameKey, skipXamlCompilation, debuggerLaunch:false); } internal static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, - string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool patchCom, bool skipXamlCompilation, bool debuggerLaunch) + string output, bool verifyIl, MessageImportance logImportance, string strongNameKey, bool skipXamlCompilation, bool debuggerLaunch) { var typeSystem = new CecilTypeSystem( references.Where(r => !r.ToLowerInvariant().EndsWith("avalonia.build.tasks.dll")), @@ -58,15 +58,12 @@ namespace Avalonia.Build.Tasks if (!skipXamlCompilation) { var compileRes = CompileCore(engine, typeSystem, projectDirectory, verifyIl, logImportance, debuggerLaunch); - if (compileRes == null && !patchCom) + if (compileRes == null) return new CompileResult(true); if (compileRes == false) return new CompileResult(false); } - if (patchCom) - ComInteropHelper.PatchAssembly(asm, typeSystem); - var writerParameters = new WriterParameters { WriteSymbols = asm.MainModule.HasSymbols }; if (!string.IsNullOrWhiteSpace(strongNameKey)) writerParameters.StrongNameKeyBlob = File.ReadAllBytes(strongNameKey); @@ -112,9 +109,8 @@ namespace Avalonia.Build.Tasks } } var asm = typeSystem.TargetAssemblyDefinition; - var emres = new EmbeddedResources(asm); var avares = new AvaloniaResources(asm, projectDirectory); - if (avares.Resources.Count(CheckXamlName) == 0 && emres.Resources.Count(CheckXamlName) == 0) + if (avares.Resources.Count(CheckXamlName) == 0) // Nothing to do return null; @@ -436,9 +432,6 @@ namespace Avalonia.Build.Tasks return true; } - if (emres.Resources.Count(CheckXamlName) != 0) - if (!CompileGroup(emres)) - return false; if (avares.Resources.Count(CheckXamlName) != 0) { if (!CompileGroup(avares)) diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index d7f39f6642..599d95e67a 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -3,9 +3,6 @@ netstandard2.0 true - - <_AvaloniaPatchComInterop>true - false From 39bd2f074aefbc2efcb5f52e2fb4149493240995 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 18 Oct 2022 07:58:46 +0000 Subject: [PATCH 029/122] add parameters for number styles and provider to double parse --- src/Avalonia.Base/Media/Color.cs | 10 +++---- src/Avalonia.Base/Media/HslColor.cs | 8 +++--- src/Avalonia.Base/Media/HsvColor.cs | 8 +++--- src/Avalonia.Base/Utilities/SpanHelpers.cs | 26 +++++++------------ src/Avalonia.Build.Tasks/SpanCompat.cs | 17 +++++------- .../Markup/Parsers/SelectorGrammar.cs | 4 +-- 6 files changed, 32 insertions(+), 41 deletions(-) diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 14ef357393..5470a735b3 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -296,7 +296,7 @@ namespace Avalonia.Media return false; } - if (!input.TryParseFromHexToUInt(out var parsed)) + if (!input.TryParseUInt(NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed)) { return false; } @@ -409,7 +409,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outByte = (byte)Math.Round((percentage / 100.0) * 255.0); @@ -417,7 +417,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToByte( + return inString.TryParseByte(NumberStyles.Number, CultureInfo.InvariantCulture, out outByte); } } @@ -430,7 +430,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outDouble = percentage / 100.0; @@ -438,7 +438,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToDouble( + return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out outDouble); } } diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 5a9e5cd54b..425a3138c3 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -302,7 +302,7 @@ namespace Avalonia.Media if (components.Length == 3) // HSL { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && TryInternalParse(components[1].AsSpan(), out double saturation) && TryInternalParse(components[2].AsSpan(), out double lightness)) { @@ -312,7 +312,7 @@ namespace Avalonia.Media } else if (components.Length == 4) // HSLA { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && TryInternalParse(components[1].AsSpan(), out double saturation) && TryInternalParse(components[2].AsSpan(), out double lightness) && TryInternalParse(components[3].AsSpan(), out double alpha)) @@ -330,7 +330,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outDouble = percentage / 100.0; @@ -338,7 +338,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToDouble( + return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out outDouble); } } diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 30a6c7ef4e..9f95b31518 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -302,7 +302,7 @@ namespace Avalonia.Media if (components.Length == 3) // HSV { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && TryInternalParse(components[1].AsSpan(), out double saturation) && TryInternalParse(components[2].AsSpan(), out double value)) { @@ -312,7 +312,7 @@ namespace Avalonia.Media } else if (components.Length == 4) // HSVA { - if (components[0].AsSpan().TryParseNumberToDouble(out double hue) && + if (components[0].AsSpan().TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double hue) && TryInternalParse(components[1].AsSpan(), out double saturation) && TryInternalParse(components[2].AsSpan(), out double value) && TryInternalParse(components[3].AsSpan(), out double alpha)) @@ -330,7 +330,7 @@ namespace Avalonia.Media if (percentIndex >= 0) { - var result = inString.Slice(0, percentIndex).TryParseNumberToDouble( + var result = inString.Slice(0, percentIndex).TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out double percentage); outDouble = percentage / 100.0; @@ -338,7 +338,7 @@ namespace Avalonia.Media } else { - return inString.TryParseNumberToDouble( + return inString.TryParseDouble(NumberStyles.Number, CultureInfo.InvariantCulture, out outDouble); } } diff --git a/src/Avalonia.Base/Utilities/SpanHelpers.cs b/src/Avalonia.Base/Utilities/SpanHelpers.cs index 6b779dfd87..9a5dce9798 100644 --- a/src/Avalonia.Base/Utilities/SpanHelpers.cs +++ b/src/Avalonia.Base/Utilities/SpanHelpers.cs @@ -7,19 +7,17 @@ namespace Avalonia.Utilities public static class SpanHelpers { [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseFromHexToUInt(this ReadOnlySpan span, out uint value) + public static bool TryParseUInt(this ReadOnlySpan span, NumberStyles style, IFormatProvider provider, out uint value) { #if NETSTANDARD2_0 - return uint.TryParse(span.ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out value); + return uint.TryParse(span.ToString(), style, provider, out value); #else - return uint.TryParse(span, NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out value); + return uint.TryParse(span, style, provider, out value); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseToInt(this ReadOnlySpan span, out int value) + public static bool TryParseInt(this ReadOnlySpan span, out int value) { #if NETSTANDARD2_0 return int.TryParse(span.ToString(), out value); @@ -29,26 +27,22 @@ namespace Avalonia.Utilities } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseNumberToDouble(this ReadOnlySpan span, out double value) + public static bool TryParseDouble(this ReadOnlySpan span, NumberStyles style, IFormatProvider provider, out double value) { #if NETSTANDARD2_0 - return double.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return double.TryParse(span.ToString(), style, provider, out value); #else - return double.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return double.TryParse(span, style, provider, out value); #endif } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool TryParseNumberToByte(this ReadOnlySpan span, out byte value) + public static bool TryParseByte(this ReadOnlySpan span, NumberStyles style, IFormatProvider provider, out byte value) { #if NETSTANDARD2_0 - return byte.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return byte.TryParse(span.ToString(), style, provider, out value); #else - return byte.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return byte.TryParse(span, style, provider, out value); #endif } } diff --git a/src/Avalonia.Build.Tasks/SpanCompat.cs b/src/Avalonia.Build.Tasks/SpanCompat.cs index af78178b5e..be59ff8b6c 100644 --- a/src/Avalonia.Build.Tasks/SpanCompat.cs +++ b/src/Avalonia.Build.Tasks/SpanCompat.cs @@ -87,30 +87,27 @@ namespace System } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseFromHexToUInt(out uint value) + public bool TryParseUInt(NumberStyles style, IFormatProvider provider, out uint value) { - return uint.TryParse(ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, - out value); + return uint.TryParse(ToString(), style, provider, out value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseToInt(out int value) + public bool TryParseInt(out int value) { return int.TryParse(ToString(), out value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseNumberToDouble(out double value) + public bool TryParseDouble(NumberStyles style, IFormatProvider provider, out double value) { - return double.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return double.TryParse(ToString(), style, provider, out value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool TryParseNumberToByte(out byte value) + public bool TryParseByte(NumberStyles style, IFormatProvider provider, out byte value) { - return byte.TryParse(ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, - out value); + return byte.TryParse(ToString(), style, provider, out value); } public override string ToString() => _length == 0 ? string.Empty : _s.Substring(_start, _length); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index a5e11cd233..4d6d16a3ce 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -417,7 +417,7 @@ namespace Avalonia.Markup.Parsers { stepOrOffset = -1; } - else if (!stepOrOffsetStr.TryParseToInt(out stepOrOffset)) + else if (!stepOrOffsetStr.TryParseInt(out stepOrOffset)) { throw new ExpressionParseException(r.Position, "Couldn't parse nth-child step or offset value. Integer was expected."); } @@ -462,7 +462,7 @@ namespace Avalonia.Markup.Parsers r.SkipWhitespace(); if (sign != 0 - && !r.TakeUntil(')').TryParseToInt(out offset)) + && !r.TakeUntil(')').TryParseInt(out offset)) { throw new ExpressionParseException(r.Position, "Couldn't parse nth-child offset value. Integer was expected."); } From 8510a1ba8ac813f5e5fc146f593cefa46b7cf675 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Oct 2022 10:56:12 +0100 Subject: [PATCH 030/122] fix build. --- src/Markup/Avalonia.Markup/Avalonia.Markup.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index 6711c3dd3d..6b25cbbeab 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -2,6 +2,7 @@ net6.0;netstandard2.0 Avalonia + 11 From 1fea5b187501f33b0f55a0d40fbfe691420e8f73 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Oct 2022 11:22:41 +0100 Subject: [PATCH 031/122] fix langversion compile. --- .../Avalonia.Markup.Xaml.Loader.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj index debdece60f..f9be3fd62a 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj @@ -5,7 +5,7 @@ true Avalonia.Markup.Xaml.Loader $(DefineConstants);XAMLX_INTERNAL - 10 + 11 From 91b7c11641408454d69eeb042474cf9b635de39e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Oct 2022 11:26:09 +0100 Subject: [PATCH 032/122] fix integration tests on c# 11 --- azure-pipelines-integrationtests.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml index ee8abb75c1..4fba4ca36f 100644 --- a/azure-pipelines-integrationtests.yml +++ b/azure-pipelines-integrationtests.yml @@ -12,6 +12,16 @@ jobs: name: 'AvaloniaMacPool' steps: + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 6.0.401' + inputs: + version: 6.0.401 + + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + inputs: + version: 7.0.100-rc.2.22477.23 + - script: system_profiler SPDisplaysDataType |grep Resolution - script: | @@ -45,6 +55,11 @@ jobs: inputs: version: 6.0.401 + - task: UseDotNet@2 + displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + inputs: + version: 7.0.100-rc.2.22477.23 + - task: Windows Application Driver@0 inputs: OperationType: 'Start' From 95ad8cc32430e10176b8ae7e59792717bc000434 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Oct 2022 13:58:35 +0100 Subject: [PATCH 033/122] try and make test more reliable. --- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 7eeab2f7f2..2dd849bee1 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -109,7 +109,7 @@ namespace Avalonia.IntegrationTests.Appium using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.Manual)) { - mainWindow.Click(); + mainWindow.SendClick(); var secondaryWindowIndex = GetWindowOrder("SecondaryWindow"); Assert.Equal(1, secondaryWindowIndex); } From 46e451e297a298eaebc977ae9d3f0299d686b2c8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Oct 2022 14:19:20 +0100 Subject: [PATCH 034/122] pin sdk. --- global.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/global.json b/global.json index 44d4e10dbf..dc6da556b3 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,8 @@ { + "sdk": { + "version": "7.0.100-rc.2.22477.23", + "rollForward": "latestFeature" + }, "msbuild-sdks": { "Microsoft.Build.Traversal": "1.0.43", "MSBuild.Sdk.Extras": "3.0.22", From 46e13637adb88fe297b7c43d96ec4cb6904a5384 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 18 Oct 2022 19:32:33 +0600 Subject: [PATCH 035/122] Fixed Avalonia.DesignerSupport.TestApp --- .../Avalonia.DesignerSupport.TestApp.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj index 278b0e087e..a91cf3911a 100644 --- a/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj +++ b/tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj @@ -8,9 +8,9 @@ %(Filename) - + Designer - + From a763255f3f5f1ac9df6c7d3efc3bcc197e533b33 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 18 Oct 2022 17:10:52 +0200 Subject: [PATCH 036/122] fix: revert test changes --- .../Media/TextFormatting/GraphemeBreakTestDataGenerator.cs | 4 ++-- tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs | 2 +- tests/Avalonia.Controls.UnitTests/TextBoxTests.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs index 9b2cb481e0..029f8e236c 100644 --- a/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs +++ b/tests/Avalonia.Base.UnitTests/Media/TextFormatting/GraphemeBreakTestDataGenerator.cs @@ -68,7 +68,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting var graphemeChars = elements[0].Replace(" × ", " ").Split(' '); - var codepoints = graphemeChars.Where(x => !string.IsNullOrEmpty(x) && x != "×") + var codepoints = graphemeChars.Where(x => x != "" && x != "×") .Select(x => Convert.ToInt32(x, 16)).ToList(); var grapheme = codepoints.ToArray(); @@ -77,7 +77,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { var remainingChars = elements[1].Replace(" × ", " ").Split(' '); - var remaining = remainingChars.Where(x => !string.IsNullOrEmpty(x) && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); + var remaining = remainingChars.Where(x => x != "" && x != "×").Select(x => Convert.ToInt32(x, 16)).ToArray(); codepoints.AddRange(remaining); } diff --git a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs index 122176fbe0..c8bd289e54 100644 --- a/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/AutoCompleteBoxTests.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.UnitTests textbox.Text = String.Empty; Dispatcher.UIThread.RunJobs(); - Assert.True(string.IsNullOrEmpty(control.SearchText)); + Assert.True(control.SearchText == String.Empty); Assert.False(control.IsDropDownOpen); Assert.True(closeEvent); }); diff --git a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs index a2d0842028..d1fa522206 100644 --- a/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs @@ -559,7 +559,7 @@ namespace Avalonia.Controls.UnitTests Text = "0123456789" }; - Assert.True(string.IsNullOrEmpty(target.SelectedText)); + Assert.True(target.SelectedText == ""); target.SelectionStart = 2; target.SelectionEnd = 4; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index f2982046e5..23a330c96f 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -473,7 +473,7 @@ namespace Avalonia.Controls.UnitTests target.ApplyTemplate(); - Assert.True(string.IsNullOrEmpty(target.SelectedText)); + Assert.True(target.SelectedText == ""); target.SelectionStart = 2; target.SelectionEnd = 4; From cb0993b5fc5615402fa17aa5e3fd447a83277687 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 18 Oct 2022 17:19:10 +0200 Subject: [PATCH 037/122] fix: double declaration OutputType --- tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 3f4978f544..754a1d6a24 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -1,6 +1,5 @@  - Exe net6.0 Exe false From 813476a1a565cb5307c35c29a16e722ba3bef9dc Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Oct 2022 16:42:05 +0100 Subject: [PATCH 038/122] fix designer tests. --- samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj | 1 + .../Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj | 2 +- tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index 0667644643..6c17e9ac43 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -5,6 +5,7 @@ net6.0 true true + 6.0.9 diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 0c0fe5b921..cab8f22f47 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -1,7 +1,7 @@  Exe - net461;netcoreapp2.0 + netstandard2.1 diff --git a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs index 6e32158fcb..c20153433a 100644 --- a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs +++ b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs @@ -19,7 +19,7 @@ namespace Avalonia.DesignerSupport.Tests { public class DesignerSupportTests { - private const string DesignerAppPath = "../../../../../src/tools/Avalonia.Designer.HostApp/bin/$BUILD/netcoreapp2.0/Avalonia.Designer.HostApp.dll"; + private const string DesignerAppPath = "../../../../../src/tools/Avalonia.Designer.HostApp/bin/$BUILD/netstandard2.1/Avalonia.Designer.HostApp.dll"; private readonly Xunit.Abstractions.ITestOutputHelper outputHelper; public DesignerSupportTests(Xunit.Abstractions.ITestOutputHelper outputHelper) From 253267c821ebd556a586c5b0d2e1701a9cfe3290 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 18 Oct 2022 17:02:28 +0100 Subject: [PATCH 039/122] make host app target netfx / nca 2.0 --- .../Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj | 2 +- tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index cab8f22f47..0c0fe5b921 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -1,7 +1,7 @@  Exe - netstandard2.1 + net461;netcoreapp2.0 diff --git a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs index c20153433a..6e32158fcb 100644 --- a/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs +++ b/tests/Avalonia.DesignerSupport.Tests/DesignerSupportTests.cs @@ -19,7 +19,7 @@ namespace Avalonia.DesignerSupport.Tests { public class DesignerSupportTests { - private const string DesignerAppPath = "../../../../../src/tools/Avalonia.Designer.HostApp/bin/$BUILD/netstandard2.1/Avalonia.Designer.HostApp.dll"; + private const string DesignerAppPath = "../../../../../src/tools/Avalonia.Designer.HostApp/bin/$BUILD/netcoreapp2.0/Avalonia.Designer.HostApp.dll"; private readonly Xunit.Abstractions.ITestOutputHelper outputHelper; public DesignerSupportTests(Xunit.Abstractions.ITestOutputHelper outputHelper) From 48c91fbac7ebc1a1a96b599000ddc0eabb866232 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 18 Oct 2022 19:07:05 +0200 Subject: [PATCH 040/122] fix: disable NETAnalyzers for tests --- tests/Directory.Build.props | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/Directory.Build.props diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000000..52af0f5ab2 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,6 @@ + + + + false + + From 6bc689a662cd9f8154fed77fd6aa13e494cbb3ad Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 18 Oct 2022 21:10:59 -0400 Subject: [PATCH 041/122] Updates from code review --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Platform/Screen.cs | 35 +++++++++++++++------- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.X11/X11Window.cs | 2 +- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 8 ++--- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 6a4ed5a890..119bc09888 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -62,7 +62,7 @@ namespace ControlCatalog.Pages CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); - formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%"); + formattedText = CreateFormattedText($"Scaling: {screen.Scale * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); formattedText = CreateFormattedText($"Primary: {screen.IsPrimary}"); diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index cdab81705f..e5b8ca5555 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -1,4 +1,6 @@ -namespace Avalonia.Platform +using System; + +namespace Avalonia.Platform { /// /// Represents a single display screen. @@ -6,34 +8,47 @@ public class Screen { /// - /// Gets the pixel density of the screen. - /// This is a scaling factor so multiply by 100 to get a percentage. + /// Gets the scaling factor applied to the screen by the operating system. /// /// - /// Both X and Y density are assumed uniform. + /// Multiply this value by 100 to get a percentage. + /// Both X and Y scaling factors are assumed uniform. /// - public double PixelDensity { get; } + public double Scale { get; } + + /// + [Obsolete("Use the Scale property instead.")] + public double PixelDensity => Scale; /// /// Gets the overall pixel-size of the screen. - /// This generally is the raw pixel counts in both the X and Y direction. /// + /// + /// This generally is the raw pixel counts in both the X and Y direction. + /// public PixelRect Bounds { get; } /// /// Gets the actual working-area pixel-size of the screen. - /// This may be smaller to account for notches and other block-out areas. /// + /// + /// This area may be smaller than to account for notches and + /// other block-out areas such as taskbars etc. + /// public PixelRect WorkingArea { get; } /// /// Gets a value indicating whether the screen is the primary one. /// public bool IsPrimary { get; } - - public Screen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool isPrimary) + + /// + [Obsolete("Use the IsPrimary property instead.")] + public bool Primary => IsPrimary; + + public Screen(double scale, PixelRect bounds, PixelRect workingArea, bool isPrimary) { - this.PixelDensity = pixelDensity; + this.Scale = scale; this.Bounds = bounds; this.WorkingArea = workingArea; this.IsPrimary = isPrimary; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 34de439c94..8bb2b0a713 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -92,7 +92,7 @@ namespace Avalonia.Native _savedScaling = RenderScaling; _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost()); - var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + var monitor = Screen.AllScreens.OrderBy(x => x.Scale) .FirstOrDefault(m => m.Bounds.Contains(Position)); Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index f24c33cafa..75b741ac77 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -120,7 +120,7 @@ namespace Avalonia.X11 if (!_popup && Screen != null) { - var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity) + var monitor = Screen.AllScreens.OrderBy(x => x.Scale) .FirstOrDefault(m => m.Bounds.Contains(Position)); if (monitor != null) diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 346d6e5adb..93bdfda652 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -216,7 +216,7 @@ namespace Avalonia.Win32 { Anchor = PopupAnchor.TopLeft, Gravity = PopupGravity.BottomRight, - AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.PixelDensity, new Size(1, 1)), + AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scale, new Size(1, 1)), Size = finalRect.Size, ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY, }); @@ -244,16 +244,16 @@ namespace Avalonia.Win32 { var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft; var size = _hiddenWindow.Screens.Primary.Bounds.Size; - return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.PixelDensity, size.Height * _hiddenWindow.Screens.Primary.PixelDensity); + return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scale, size.Height * _hiddenWindow.Screens.Primary.Scale); } } public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.PixelDensity); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scale); } - public double Scaling => _hiddenWindow.Screens.Primary.PixelDensity; + public double Scaling => _hiddenWindow.Screens.Primary.Scale; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9741f3f804..cd8515eaa6 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -224,7 +224,7 @@ namespace Avalonia.Win32 } } - private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.PixelDensity ?? 1; + private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scale ?? 1; public double RenderScaling => _scaling; From f271654d64885ffae707bf00e7b787a1ec39c62a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 19 Oct 2022 09:49:06 +0200 Subject: [PATCH 042/122] fix: CS8350 --- src/Avalonia.Base/Media/PathMarkupParser.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Media/PathMarkupParser.cs b/src/Avalonia.Base/Media/PathMarkupParser.cs index 30c5206125..cf12bf5126 100644 --- a/src/Avalonia.Base/Media/PathMarkupParser.cs +++ b/src/Avalonia.Base/Media/PathMarkupParser.cs @@ -188,7 +188,7 @@ namespace Avalonia.Media _isOpen = true; } - private void SetFillRule(ref ReadOnlySpan span) + private void SetFillRule(scoped ref ReadOnlySpan span) { ThrowIfDisposed(); @@ -452,7 +452,7 @@ namespace Avalonia.Media return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0])); } - private static bool ReadArgument(ref ReadOnlySpan remaining, out ReadOnlySpan argument) + private static bool ReadArgument(scoped ref ReadOnlySpan remaining, out ReadOnlySpan argument) { remaining = SkipWhitespace(remaining); if (remaining.IsEmpty) From 48bd894e6059f9c2062e5a35ccc5afc134cdeb25 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 19 Oct 2022 18:00:25 +0600 Subject: [PATCH 043/122] Use MicroCom.Runtime from nuget --- .../Avalonia.MicroCom.csproj | 1 + src/Avalonia.MicroCom/CallbackBase.cs | 4 +- .../IMicroComExceptionCallback.cs | 9 - .../IMicroComShadowContainer.cs | 9 - src/Avalonia.MicroCom/IUnknown.cs | 8 - src/Avalonia.MicroCom/LocalInterop.cs | 17 -- src/Avalonia.MicroCom/MicroComProxyBase.cs | 110 ----------- src/Avalonia.MicroCom/MicroComRuntime.cs | 143 -------------- src/Avalonia.MicroCom/MicroComShadow.cs | 177 ------------------ src/Avalonia.MicroCom/MicroComVtblBase.cs | 47 ----- src/Avalonia.Native/Avalonia.Native.csproj | 1 - src/Avalonia.Native/AvaloniaNativePlatform.cs | 1 + src/Avalonia.Native/CallbackBase.cs | 1 + src/Avalonia.Native/NativeControlHostImpl.cs | 1 + .../Avalonia.Win32/Avalonia.Win32.csproj | 1 - src/Windows/Avalonia.Win32/ClipboardImpl.cs | 7 +- src/Windows/Avalonia.Win32/DragSource.cs | 5 +- .../Interop/UnmanagedMethods.cs | 1 + src/Windows/Avalonia.Win32/OleContext.cs | 3 +- src/Windows/Avalonia.Win32/OleDataObject.cs | 2 +- src/Windows/Avalonia.Win32/OleDropTarget.cs | 1 + .../Avalonia.Win32/Win32StorageProvider.cs | 1 + .../Composition/WinUICompositedWindow.cs | 1 + .../Composition/WinUICompositorConnection.cs | 1 + .../WinRT/Composition/WinUIEffectBase.cs | 1 + .../WinUiCompositedWindowSurface.cs | 1 + .../WinRT/NativeWinRTMethods.cs | 1 + .../Avalonia.Win32/WinRT/WinRTInspectable.cs | 1 + 28 files changed, 26 insertions(+), 530 deletions(-) delete mode 100644 src/Avalonia.MicroCom/IMicroComExceptionCallback.cs delete mode 100644 src/Avalonia.MicroCom/IMicroComShadowContainer.cs delete mode 100644 src/Avalonia.MicroCom/IUnknown.cs delete mode 100644 src/Avalonia.MicroCom/LocalInterop.cs delete mode 100644 src/Avalonia.MicroCom/MicroComProxyBase.cs delete mode 100644 src/Avalonia.MicroCom/MicroComRuntime.cs delete mode 100644 src/Avalonia.MicroCom/MicroComShadow.cs delete mode 100644 src/Avalonia.MicroCom/MicroComVtblBase.cs diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index d7f39f6642..43ae612699 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -7,6 +7,7 @@ <_AvaloniaPatchComInterop>true + false all diff --git a/src/Avalonia.MicroCom/CallbackBase.cs b/src/Avalonia.MicroCom/CallbackBase.cs index 6783ebe3dc..49d589a5fc 100644 --- a/src/Avalonia.MicroCom/CallbackBase.cs +++ b/src/Avalonia.MicroCom/CallbackBase.cs @@ -1,4 +1,6 @@ -namespace Avalonia.MicroCom +using MicroCom.Runtime; + +namespace Avalonia.MicroCom { public abstract class CallbackBase : IUnknown, IMicroComShadowContainer { diff --git a/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs b/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs deleted file mode 100644 index 08f20339ec..0000000000 --- a/src/Avalonia.MicroCom/IMicroComExceptionCallback.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Avalonia.MicroCom -{ - public interface IMicroComExceptionCallback - { - void RaiseException(Exception e); - } -} diff --git a/src/Avalonia.MicroCom/IMicroComShadowContainer.cs b/src/Avalonia.MicroCom/IMicroComShadowContainer.cs deleted file mode 100644 index a33d3a9811..0000000000 --- a/src/Avalonia.MicroCom/IMicroComShadowContainer.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Avalonia.MicroCom -{ - public interface IMicroComShadowContainer - { - MicroComShadow Shadow { get; set; } - void OnReferencedFromNative(); - void OnUnreferencedFromNative(); - } -} diff --git a/src/Avalonia.MicroCom/IUnknown.cs b/src/Avalonia.MicroCom/IUnknown.cs deleted file mode 100644 index 0dc4106423..0000000000 --- a/src/Avalonia.MicroCom/IUnknown.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Avalonia.MicroCom -{ - public interface IUnknown : IDisposable - { - } -} diff --git a/src/Avalonia.MicroCom/LocalInterop.cs b/src/Avalonia.MicroCom/LocalInterop.cs deleted file mode 100644 index 785f4e03a5..0000000000 --- a/src/Avalonia.MicroCom/LocalInterop.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Avalonia.MicroCom -{ - unsafe class LocalInterop - { - public static unsafe void CalliStdCallvoid(void* thisObject, void* methodPtr) - { - throw null; - } - - public static unsafe int CalliStdCallint(void* thisObject, Guid* guid, IntPtr* ppv, void* methodPtr) - { - throw null; - } - } -} diff --git a/src/Avalonia.MicroCom/MicroComProxyBase.cs b/src/Avalonia.MicroCom/MicroComProxyBase.cs deleted file mode 100644 index fe8a2bf9cf..0000000000 --- a/src/Avalonia.MicroCom/MicroComProxyBase.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Avalonia.MicroCom -{ - public unsafe class MicroComProxyBase : CriticalFinalizerObject, IUnknown - { - private IntPtr _nativePointer; - private bool _ownsHandle; - private SynchronizationContext _synchronizationContext; - - public IntPtr NativePointer - { - get - { - if (_nativePointer == IntPtr.Zero) - throw new ObjectDisposedException(this.GetType().FullName); - return _nativePointer; - } - } - - public void*** PPV => (void***)NativePointer; - - public MicroComProxyBase(IntPtr nativePointer, bool ownsHandle) - { - _nativePointer = nativePointer; - _ownsHandle = ownsHandle; - _synchronizationContext = SynchronizationContext.Current; - if(!_ownsHandle) - GC.SuppressFinalize(this); - } - - protected virtual int VTableSize => 3; - - public void AddRef() - { - LocalInterop.CalliStdCallvoid(PPV, (*PPV)[1]); - } - - public void Release() - { - LocalInterop.CalliStdCallvoid(PPV, (*PPV)[2]); - } - - public int QueryInterface(Guid guid, out IntPtr ppv) - { - IntPtr r = default; - var rv = LocalInterop.CalliStdCallint(PPV, &guid, &r, (*PPV)[0]); - ppv = r; - return rv; - } - - public T QueryInterface() where T : IUnknown - { - var guid = MicroComRuntime.GetGuidFor(typeof(T)); - var rv = QueryInterface(guid, out var ppv); - if (rv == 0) - return (T)MicroComRuntime.CreateProxyFor(typeof(T), ppv, true); - throw new COMException("QueryInterface failed", rv); - } - - public bool IsDisposed => _nativePointer == IntPtr.Zero; - - protected virtual void Dispose(bool disposing) - { - if(_nativePointer == IntPtr.Zero) - return; - if (_ownsHandle) - { - Release(); - _ownsHandle = false; - } - _nativePointer = IntPtr.Zero; - GC.SuppressFinalize(this); - } - - public void Dispose() => Dispose(true); - - public bool OwnsHandle => _ownsHandle; - - public void EnsureOwned() - { - if (!_ownsHandle) - { - GC.ReRegisterForFinalize(true); - AddRef(); - _ownsHandle = true; - } - } - - private static readonly SendOrPostCallback _disposeDelegate = DisposeOnContext; - - private static void DisposeOnContext(object state) - { - (state as MicroComProxyBase)?.Dispose(false); - } - - ~MicroComProxyBase() - { - if(!_ownsHandle) - return; - if (_synchronizationContext == null) - Dispose(); - else - _synchronizationContext.Post(_disposeDelegate, this); - } - } -} diff --git a/src/Avalonia.MicroCom/MicroComRuntime.cs b/src/Avalonia.MicroCom/MicroComRuntime.cs deleted file mode 100644 index b9a56a69ba..0000000000 --- a/src/Avalonia.MicroCom/MicroComRuntime.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Runtime.InteropServices; - -namespace Avalonia.MicroCom -{ - public static unsafe class MicroComRuntime - { - private static ConcurrentDictionary _vtables = new ConcurrentDictionary(); - - private static ConcurrentDictionary> _factories = - new ConcurrentDictionary>(); - private static ConcurrentDictionary _guids = new ConcurrentDictionary(); - private static ConcurrentDictionary _guidsToTypes = new ConcurrentDictionary(); - - internal static readonly Guid ManagedObjectInterfaceGuid = Guid.Parse("cd7687c0-a9c2-4563-b08e-a399df50c633"); - - static MicroComRuntime() - { - Register(typeof(IUnknown), new Guid("00000000-0000-0000-C000-000000000046"), - (ppv, owns) => new MicroComProxyBase(ppv, owns)); - RegisterVTable(typeof(IUnknown), MicroComVtblBase.Vtable); - } - - public static void RegisterVTable(Type t, IntPtr vtable) - { - _vtables[t] = vtable; - } - - public static void Register(Type t, Guid guid, Func proxyFactory) - { - _factories[t] = proxyFactory; - _guids[t] = guid; - _guidsToTypes[guid] = t; - } - - public static Guid GetGuidFor(Type type) => _guids[type]; - - public static T CreateProxyFor(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); - public static T CreateProxyFor(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle); - - public static T CreateProxyOrNullFor(void* pObject, bool ownsHandle) where T : class => - pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); - - public static T CreateProxyOrNullFor(IntPtr pObject, bool ownsHandle) where T : class => - pObject == IntPtr.Zero ? null : (T)CreateProxyFor(typeof(T), pObject, ownsHandle); - - public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle); - - public static IntPtr GetNativeIntPtr(this T obj, bool owned = false) where T : IUnknown - => new IntPtr(GetNativePointer(obj, owned)); - public static void* GetNativePointer(T obj, bool owned = false) where T : IUnknown - { - if (obj == null) - return null; - if (obj is MicroComProxyBase proxy) - { - if(owned) - proxy.AddRef(); - return (void*)proxy.NativePointer; - } - - if (obj is IMicroComShadowContainer container) - { - container.Shadow ??= new MicroComShadow(container); - void* ptr = null; - var res = container.Shadow.GetOrCreateNativePointer(typeof(T), &ptr); - if (res != 0) - throw new COMException( - "Unable to create native callable wrapper for type " + typeof(T) + " for instance of type " + - obj.GetType(), - res); - if (owned) - container.Shadow.AddRef((Ccw*)ptr); - return ptr; - } - throw new ArgumentException("Unable to get a native pointer for " + obj); - } - - public static object GetObjectFromCcw(IntPtr ccw) - { - var ptr = (Ccw*)ccw; - var shadow = (MicroComShadow)GCHandle.FromIntPtr(ptr->GcShadowHandle).Target; - return shadow.Target; - } - - public static bool IsComWrapper(IUnknown obj) => obj is MicroComProxyBase; - - public static object TryUnwrapManagedObject(IUnknown obj) - { - if (obj is not MicroComProxyBase proxy) - return null; - if (proxy.QueryInterface(ManagedObjectInterfaceGuid, out _) != 0) - return null; - // Successful QueryInterface always increments ref counter - proxy.Release(); - return GetObjectFromCcw(proxy.NativePointer); - } - - public static bool TryGetTypeForGuid(Guid guid, out Type t) => _guidsToTypes.TryGetValue(guid, out t); - - public static bool GetVtableFor(Type type, out IntPtr ptr) => _vtables.TryGetValue(type, out ptr); - - public static void UnhandledException(object target, Exception e) - { - if (target is IMicroComExceptionCallback cb) - { - try - { - cb.RaiseException(e); - } - catch - { - // We've tried - } - } - - } - - public static T CloneReference(this T iface) where T : IUnknown - { - var proxy = (MicroComProxyBase)(object)iface; - var ownedPointer = GetNativePointer(iface, true); - return CreateProxyFor(ownedPointer, true); - } - - public static T QueryInterface(this IUnknown unknown) where T : IUnknown - { - var proxy = (MicroComProxyBase)unknown; - return proxy.QueryInterface(); - } - - public static void UnsafeAddRef(this IUnknown unknown) - { - ((MicroComProxyBase)unknown).AddRef(); - } - - public static void UnsafeRelease(this IUnknown unknown) - { - ((MicroComProxyBase)unknown).Release(); - } - } -} diff --git a/src/Avalonia.MicroCom/MicroComShadow.cs b/src/Avalonia.MicroCom/MicroComShadow.cs deleted file mode 100644 index 765ff3b9ad..0000000000 --- a/src/Avalonia.MicroCom/MicroComShadow.cs +++ /dev/null @@ -1,177 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Avalonia.MicroCom -{ - public unsafe class MicroComShadow : IDisposable - { - private readonly object _lock = new object(); - private readonly Dictionary _shadows = new Dictionary(); - private readonly Dictionary _backShadows = new Dictionary(); - private GCHandle? _handle; - private volatile int _refCount; - internal IMicroComShadowContainer Target { get; } - internal MicroComShadow(IMicroComShadowContainer target) - { - Target = target; - Target.Shadow = this; - } - - internal int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) - { - if (MicroComRuntime.TryGetTypeForGuid(*guid, out var type)) - return QueryInterface(type, ppv); - else if (*guid == MicroComRuntime.ManagedObjectInterfaceGuid) - { - ccw->RefCount++; - *ppv = ccw; - return 0; - } - else - return unchecked((int)0x80004002u); - } - - internal int QueryInterface(Type type, void** ppv) - { - if (!type.IsInstanceOfType(Target)) - return unchecked((int)0x80004002u); - - var rv = GetOrCreateNativePointer(type, ppv); - if (rv == 0) - AddRef((Ccw*)*ppv); - return rv; - } - - internal int GetOrCreateNativePointer(Type type, void** ppv) - { - if (!MicroComRuntime.GetVtableFor(type, out var vtable)) - return unchecked((int)0x80004002u); - lock (_lock) - { - - if (_shadows.TryGetValue(type, out var shadow)) - { - var targetCcw = (Ccw*)shadow; - AddRef(targetCcw); - *ppv = targetCcw; - return 0; - } - else - { - var intPtr = Marshal.AllocHGlobal(Marshal.SizeOf()); - var targetCcw = (Ccw*)intPtr; - *targetCcw = default; - targetCcw->RefCount = 0; - targetCcw->VTable = vtable; - if (_handle == null) - _handle = GCHandle.Alloc(this); - targetCcw->GcShadowHandle = GCHandle.ToIntPtr(_handle.Value); - _shadows[type] = intPtr; - _backShadows[intPtr] = type; - *ppv = targetCcw; - - return 0; - } - } - } - - internal int AddRef(Ccw* ccw) - { - if (Interlocked.Increment(ref _refCount) == 1) - { - try - { - Target.OnReferencedFromNative(); - } - catch (Exception e) - { - MicroComRuntime.UnhandledException(Target, e); - } - } - - return Interlocked.Increment(ref ccw->RefCount); - } - - internal int Release(Ccw* ccw) - { - Interlocked.Decrement(ref _refCount); - var cnt = Interlocked.Decrement(ref ccw->RefCount); - if (cnt == 0) - return FreeCcw(ccw); - - return cnt; - } - - int FreeCcw(Ccw* ccw) - { - lock (_lock) - { - // Shadow got resurrected by a call to QueryInterface from another thread - if (ccw->RefCount != 0) - return ccw->RefCount; - - var intPtr = new IntPtr(ccw); - var type = _backShadows[intPtr]; - _backShadows.Remove(intPtr); - _shadows.Remove(type); - Marshal.FreeHGlobal(intPtr); - if (_shadows.Count == 0) - { - _handle?.Free(); - _handle = null; - try - { - Target.OnUnreferencedFromNative(); - } - catch(Exception e) - { - MicroComRuntime.UnhandledException(Target, e); - } - } - } - - return 0; - } - - /* - Needs to be called to support the following scenario: - 1) Object created - 2) Object passed to native code, shadow is created, CCW is created - 3) Native side has never called AddRef - - In that case the GC handle to the shadow object is still alive - */ - - public void Dispose() - { - lock (_lock) - { - List toRemove = null; - foreach (var kv in _backShadows) - { - var ccw = (Ccw*)kv.Key; - if (ccw->RefCount == 0) - { - toRemove ??= new List(); - toRemove.Add(kv.Key); - } - } - - if(toRemove != null) - foreach (var intPtr in toRemove) - FreeCcw((Ccw*)intPtr); - } - } - } - - [StructLayout(LayoutKind.Sequential)] - struct Ccw - { - public IntPtr VTable; - public IntPtr GcShadowHandle; - public volatile int RefCount; - public MicroComShadow GetShadow() => (MicroComShadow)GCHandle.FromIntPtr(GcShadowHandle).Target; - } -} diff --git a/src/Avalonia.MicroCom/MicroComVtblBase.cs b/src/Avalonia.MicroCom/MicroComVtblBase.cs deleted file mode 100644 index 7092f8131a..0000000000 --- a/src/Avalonia.MicroCom/MicroComVtblBase.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; - -namespace Avalonia.MicroCom -{ - public unsafe class MicroComVtblBase - { - private List _methods = new List(); - [UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] - private delegate int AddRefDelegate(Ccw* ccw); - - [UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)] - private delegate int QueryInterfaceDelegate(Ccw* ccw, Guid* guid, void** ppv); - - public static IntPtr Vtable { get; } = new MicroComVtblBase().CreateVTable(); - public MicroComVtblBase() - { - AddMethod((QueryInterfaceDelegate)QueryInterface); - AddMethod((AddRefDelegate)AddRef); - AddMethod((AddRefDelegate)Release); - } - - protected void AddMethod(void* f) - { - _methods.Add(new IntPtr(f)); - } - - protected void AddMethod(Delegate d) - { - GCHandle.Alloc(d); - _methods.Add(Marshal.GetFunctionPointerForDelegate(d)); - } - - protected unsafe IntPtr CreateVTable() - { - var ptr = (IntPtr*)Marshal.AllocHGlobal((IntPtr.Size + 1) * _methods.Count); - for (var c = 0; c < _methods.Count; c++) - ptr[c] = _methods[c]; - return new IntPtr(ptr); - } - - static int QueryInterface(Ccw* ccw, Guid* guid, void** ppv) => ccw->GetShadow().QueryInterface(ccw, guid, ppv); - static int AddRef(Ccw* ccw) => ccw->GetShadow().AddRef(ccw); - static int Release(Ccw* ccw) => ccw->GetShadow().Release(ccw); - } -} diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 2001a2fcbc..4ceb1be340 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -6,7 +6,6 @@ true net6.0;netstandard2.0 true - Avalonia.MicroCom diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index b45fe5559b..532893884a 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -10,6 +10,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using JetBrains.Annotations; +using MicroCom.Runtime; namespace Avalonia.Native { diff --git a/src/Avalonia.Native/CallbackBase.cs b/src/Avalonia.Native/CallbackBase.cs index 56f9505cb4..2d875dbc0e 100644 --- a/src/Avalonia.Native/CallbackBase.cs +++ b/src/Avalonia.Native/CallbackBase.cs @@ -2,6 +2,7 @@ using System.Runtime.ExceptionServices; using Avalonia.MicroCom; using Avalonia.Platform; +using MicroCom.Runtime; namespace Avalonia.Native { diff --git a/src/Avalonia.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs index 2c9c1728d3..8a3488d95e 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -4,6 +4,7 @@ using Avalonia.MicroCom; using Avalonia.Native.Interop; using Avalonia.Platform; using Avalonia.VisualTree; +using MicroCom.Runtime; namespace Avalonia.Native { diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 55d02b5d31..2f0bfd86a5 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -3,7 +3,6 @@ net6.0;netstandard2.0 true Avalonia.Win32 - Avalonia.MicroCom diff --git a/src/Windows/Avalonia.Win32/ClipboardImpl.cs b/src/Windows/Avalonia.Win32/ClipboardImpl.cs index 7cf8b14bed..7e058cb052 100644 --- a/src/Windows/Avalonia.Win32/ClipboardImpl.cs +++ b/src/Windows/Avalonia.Win32/ClipboardImpl.cs @@ -7,6 +7,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32 { @@ -83,7 +84,7 @@ namespace Avalonia.Win32 while (true) { - var ptr = MicroCom.MicroComRuntime.GetNativeIntPtr(wrapper); + var ptr = wrapper.GetNativeIntPtr(); var hr = UnmanagedMethods.OleSetClipboard(ptr); if (hr == 0) @@ -107,7 +108,7 @@ namespace Avalonia.Win32 if (hr == 0) { - using var proxy = MicroCom.MicroComRuntime.CreateProxyFor(dataObject, true); + using var proxy = MicroComRuntime.CreateProxyFor(dataObject, true); using var wrapper = new OleDataObject(proxy); var formats = wrapper.GetDataFormats().ToArray(); return formats; @@ -131,7 +132,7 @@ namespace Avalonia.Win32 if (hr == 0) { - using var proxy = MicroCom.MicroComRuntime.CreateProxyFor(dataObject, true); + using var proxy = MicroComRuntime.CreateProxyFor(dataObject, true); using var wrapper = new OleDataObject(proxy); var rv = wrapper.Get(format); return rv; diff --git a/src/Windows/Avalonia.Win32/DragSource.cs b/src/Windows/Avalonia.Win32/DragSource.cs index 1159c5bfc9..53d4345c5e 100644 --- a/src/Windows/Avalonia.Win32/DragSource.cs +++ b/src/Windows/Avalonia.Win32/DragSource.cs @@ -3,6 +3,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32 { @@ -19,8 +20,8 @@ namespace Avalonia.Win32 using var src = new OleDragSource(); var allowed = OleDropTarget.ConvertDropEffect(allowedEffects); - var objPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(dataObject); - var srcPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(src); + var objPtr = MicroComRuntime.GetNativeIntPtr(dataObject); + var srcPtr = MicroComRuntime.GetNativeIntPtr(src); UnmanagedMethods.DoDragDrop(objPtr, srcPtr, (int)allowed, out var finalEffect); diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ea01d5cbdf..d1627aa13a 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices.ComTypes; using System.Text; using Avalonia.MicroCom; +using MicroCom.Runtime; using Avalonia.Win32.Win32Com; // ReSharper disable InconsistentNaming diff --git a/src/Windows/Avalonia.Win32/OleContext.cs b/src/Windows/Avalonia.Win32/OleContext.cs index c025d06fe7..e41423a334 100644 --- a/src/Windows/Avalonia.Win32/OleContext.cs +++ b/src/Windows/Avalonia.Win32/OleContext.cs @@ -5,6 +5,7 @@ using Avalonia.Platform; using Avalonia.Threading; using Avalonia.Win32.Interop; using Avalonia.Win32.Win32Com; +using MicroCom.Runtime; namespace Avalonia.Win32 { @@ -47,7 +48,7 @@ namespace Avalonia.Win32 return false; } - var trgPtr = MicroCom.MicroComRuntime.GetNativeIntPtr(target); + var trgPtr = target.GetNativeIntPtr(); return UnmanagedMethods.RegisterDragDrop(hwnd.Handle, trgPtr) == UnmanagedMethods.HRESULT.S_OK; } diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index f7345b3ff7..90992d803f 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -10,7 +10,7 @@ using Avalonia.Input; using Avalonia.MicroCom; using Avalonia.Utilities; using Avalonia.Win32.Interop; - +using MicroCom.Runtime; using IDataObject = Avalonia.Input.IDataObject; namespace Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 3d0d35228c..1c22fd025d 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -5,6 +5,7 @@ using Avalonia.Input.Raw; using Avalonia.MicroCom; using Avalonia.Platform; using Avalonia.Win32.Interop; +using MicroCom.Runtime; using DropEffect = Avalonia.Win32.Win32Com.DropEffect; namespace Avalonia.Win32 diff --git a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs index bb1de56f2b..8cc0a380f9 100644 --- a/src/Windows/Avalonia.Win32/Win32StorageProvider.cs +++ b/src/Windows/Avalonia.Win32/Win32StorageProvider.cs @@ -12,6 +12,7 @@ using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Avalonia.Win32.Interop; using Avalonia.Win32.Win32Com; +using MicroCom.Runtime; namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs index a09918a3a6..ef3de9fbe1 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositedWindow.cs @@ -6,6 +6,7 @@ using Avalonia.MicroCom; using Avalonia.OpenGL; using Avalonia.OpenGL.Egl; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 9a6bd9572a..8a41c00add 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -12,6 +12,7 @@ using Avalonia.OpenGL.Angle; using Avalonia.OpenGL.Egl; using Avalonia.Rendering; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs index ea75a2f311..32f44842f8 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Runtime.InteropServices; using Avalonia.MicroCom; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs index 4ed882552b..fc04dcda26 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindowSurface.cs @@ -6,6 +6,7 @@ using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; using Avalonia.Utilities; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT.Composition { diff --git a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs index 89cde01ff4..1794b022fe 100644 --- a/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs +++ b/src/Windows/Avalonia.Win32/WinRT/NativeWinRTMethods.cs @@ -7,6 +7,7 @@ using System.Runtime.InteropServices; using System.Threading; using Avalonia.MicroCom; using Avalonia.Win32.Interop; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT { diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs index d2ec957b8e..1fde57fe38 100644 --- a/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs +++ b/src/Windows/Avalonia.Win32/WinRT/WinRTInspectable.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Avalonia.MicroCom; +using MicroCom.Runtime; namespace Avalonia.Win32.WinRT { From 89faa52240e1fb3b1f537d09bc5bcec050dfe983 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 19 Oct 2022 16:04:09 +0200 Subject: [PATCH 044/122] Use DragEventArgs for DragLeave event. This follows the WPF/UWP API and allows pointer information to be retrieved. --- src/Avalonia.Base/Input/DragDrop.cs | 2 +- src/Avalonia.Base/Input/DragDropDevice.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Input/DragDrop.cs b/src/Avalonia.Base/Input/DragDrop.cs index 723d577964..fe75f678e1 100644 --- a/src/Avalonia.Base/Input/DragDrop.cs +++ b/src/Avalonia.Base/Input/DragDrop.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input /// /// Event which is raised, when a drag-and-drop operation leaves the element. /// - public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); + public static readonly RoutedEvent DragLeaveEvent = RoutedEvent.Register("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop)); /// /// Event which is raised, when a drag-and-drop operation is updated while over the element. /// diff --git a/src/Avalonia.Base/Input/DragDropDevice.cs b/src/Avalonia.Base/Input/DragDropDevice.cs index 30a08eda17..3c91856dcd 100644 --- a/src/Avalonia.Base/Input/DragDropDevice.cs +++ b/src/Avalonia.Base/Input/DragDropDevice.cs @@ -54,7 +54,7 @@ namespace Avalonia.Input try { if (_lastTarget != null) - _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers); return RaiseDragEvent(target, inputRoot, point, DragDrop.DragEnterEvent, effects, data, modifiers); } finally @@ -63,13 +63,13 @@ namespace Avalonia.Input } } - private void DragLeave(IInputElement inputRoot) + private void DragLeave(IInputRoot inputRoot, Point point, IDataObject data, DragDropEffects effects, KeyModifiers modifiers) { if (_lastTarget == null) return; try { - _lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent)); + RaiseDragEvent(_lastTarget, inputRoot, point, DragDrop.DragLeaveEvent, effects, data, modifiers); } finally { @@ -106,7 +106,7 @@ namespace Avalonia.Input e.Effects = DragOver(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); break; case RawDragEventType.DragLeave: - DragLeave(e.Root); + DragLeave(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); break; case RawDragEventType.Drop: e.Effects = Drop(e.Root, e.Location, e.Data, e.Effects, e.KeyModifiers); From a88fdd56b97ad1b2aa9907b03546b1da292dc8cf Mon Sep 17 00:00:00 2001 From: Benedikt Date: Wed, 19 Oct 2022 16:16:12 +0200 Subject: [PATCH 045/122] Adjust Microsoft.CodeAnalysis package version --- src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj index b16ee84b4d..fe694b5730 100644 --- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj +++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj @@ -19,7 +19,8 @@ - + + From 10b584d95c7dd903f18f706e1f64cdcaffbd18a2 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Wed, 19 Oct 2022 22:14:48 +0200 Subject: [PATCH 046/122] After the fix still causing issues, I figured the value must be >0xffff_ffff so not fitting. I changed to logic to handle that case too. --- src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f28f4fd740..3bcc395abe 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1877,7 +1877,10 @@ namespace Avalonia.Win32.Interop public static uint LGID(IntPtr HKL) { - return (uint)(HKL.ToInt64() & 0xffff); + unchecked + { + return (uint)((ulong)HKL & 0xffff); + } } public const int SORT_DEFAULT = 0; From d816726ef000545ea547bd1e24ea58be56bdb7c9 Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 19 Oct 2022 21:19:13 -0400 Subject: [PATCH 047/122] Rename Scale to Scaling and update more comments --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- src/Avalonia.Controls/Platform/IScreenImpl.cs | 6 +++++ src/Avalonia.Controls/Platform/Screen.cs | 19 ++++++++++----- src/Avalonia.Controls/Screens.cs | 13 ++++++---- src/Avalonia.Native/ScreenImpl.cs | 4 ++-- src/Avalonia.Native/WindowImplBase.cs | 2 +- src/Avalonia.Native/avn.idl | 4 ++-- src/Avalonia.X11/X11Screens.cs | 24 +++++++++++-------- src/Avalonia.X11/X11Window.cs | 8 +++---- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 8 +++---- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 11 files changed, 56 insertions(+), 36 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 119bc09888..ab75e4a4e2 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -65,7 +65,7 @@ namespace ControlCatalog.Pages formattedText = CreateFormattedText($"Scaling: {screen.Scale * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); - formattedText = CreateFormattedText($"Primary: {screen.IsPrimary}"); + formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60)); formattedText = diff --git a/src/Avalonia.Controls/Platform/IScreenImpl.cs b/src/Avalonia.Controls/Platform/IScreenImpl.cs index fcae3b6493..e2a0b3e3f3 100644 --- a/src/Avalonia.Controls/Platform/IScreenImpl.cs +++ b/src/Avalonia.Controls/Platform/IScreenImpl.cs @@ -6,8 +6,14 @@ namespace Avalonia.Platform [Unstable] public interface IScreenImpl { + /// + /// Gets the total number of screens available on the device. + /// int ScreenCount { get; } + /// + /// Gets the list of all screens available on the device. + /// IReadOnlyList AllScreens { get; } Screen? ScreenFromWindow(IWindowBaseImpl window); diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs index e5b8ca5555..4898c5f912 100644 --- a/src/Avalonia.Controls/Platform/Screen.cs +++ b/src/Avalonia.Controls/Platform/Screen.cs @@ -14,11 +14,11 @@ namespace Avalonia.Platform /// Multiply this value by 100 to get a percentage. /// Both X and Y scaling factors are assumed uniform. /// - public double Scale { get; } + public double Scaling { get; } - /// - [Obsolete("Use the Scale property instead.")] - public double PixelDensity => Scale; + /// + [Obsolete("Use the Scaling property instead.")] + public double PixelDensity => Scaling; /// /// Gets the overall pixel-size of the screen. @@ -46,9 +46,16 @@ namespace Avalonia.Platform [Obsolete("Use the IsPrimary property instead.")] public bool Primary => IsPrimary; - public Screen(double scale, PixelRect bounds, PixelRect workingArea, bool isPrimary) + /// + /// Initializes a new instance of the class. + /// + /// The scaling factor applied to the screen by the operating system. + /// The overall pixel-size of the screen. + /// The actual working-area pixel-size of the screen. + /// Whether the screen is the primary one. + public Screen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary) { - this.Scale = scale; + this.Scaling = scaling; this.Bounds = bounds; this.WorkingArea = workingArea; this.IsPrimary = isPrimary; diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs index da37959402..dde6b71e6e 100644 --- a/src/Avalonia.Controls/Screens.cs +++ b/src/Avalonia.Controls/Screens.cs @@ -16,20 +16,23 @@ namespace Avalonia.Controls private readonly IScreenImpl _iScreenImpl; /// - /// Gets the total number of screens available on this device. + /// Gets the total number of screens available on the device. /// public int ScreenCount => _iScreenImpl?.ScreenCount ?? 0; /// - /// Gets the list of all screens available on this device. + /// Gets the list of all screens available on the device. /// public IReadOnlyList All => _iScreenImpl?.AllScreens ?? Array.Empty(); /// - /// Gets the primary screen on this device. + /// Gets the primary screen on the device. /// public Screen? Primary => All.FirstOrDefault(x => x.IsPrimary); + /// + /// Initializes a new instance of the class. + /// public Screens(IScreenImpl iScreenImpl) { _iScreenImpl = iScreenImpl; @@ -39,14 +42,14 @@ namespace Avalonia.Controls { return _iScreenImpl.ScreenFromRect(bounds); } - + public Screen? ScreenFromWindow(IWindowBaseImpl window) { return _iScreenImpl.ScreenFromWindow(window); } public Screen? ScreenFromPoint(PixelPoint point) - { + { return _iScreenImpl.ScreenFromPoint(point); } diff --git a/src/Avalonia.Native/ScreenImpl.cs b/src/Avalonia.Native/ScreenImpl.cs index 83db2e8a28..53bd12cde1 100644 --- a/src/Avalonia.Native/ScreenImpl.cs +++ b/src/Avalonia.Native/ScreenImpl.cs @@ -30,10 +30,10 @@ namespace Avalonia.Native var screen = _native.GetScreen(i); result[i] = new Screen( - screen.PixelDensity, + screen.Scaling, screen.Bounds.ToAvaloniaPixelRect(), screen.WorkingArea.ToAvaloniaPixelRect(), - screen.Primary.FromComBool()); + screen.IsPrimary.FromComBool()); } return result; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 8bb2b0a713..381741cea9 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -92,7 +92,7 @@ namespace Avalonia.Native _savedScaling = RenderScaling; _nativeControlHost = new NativeControlHostImpl(_native.CreateNativeControlHost()); - var monitor = Screen.AllScreens.OrderBy(x => x.Scale) + var monitor = Screen.AllScreens.OrderBy(x => x.Scaling) .FirstOrDefault(m => m.Bounds.Contains(Position)); Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d), PlatformResizeReason.Layout); diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index a98d213887..b0bff07146 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -256,8 +256,8 @@ struct AvnScreen { AvnRect Bounds; AvnRect WorkingArea; - float PixelDensity; - bool Primary; + float Scaling; + bool IsPrimary; } enum AvnPixelFormat diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs index a65f09ee63..ba6029b350 100644 --- a/src/Avalonia.X11/X11Screens.cs +++ b/src/Avalonia.X11/X11Screens.cs @@ -218,7 +218,7 @@ namespace Avalonia.X11 public int ScreenCount => _impl.Screens.Length; public IReadOnlyList AllScreens => - _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray(); + _impl.Screens.Select(s => new Screen(s.Scaling, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray(); } interface IX11Screens @@ -285,26 +285,30 @@ namespace Avalonia.X11 public string Name { get; set; } public PixelRect Bounds { get; set; } public Size? PhysicalSize { get; set; } - public double PixelDensity { get; set; } + public double Scaling { get; set; } public PixelRect WorkingArea { get; set; } - public X11Screen(PixelRect bounds, bool primary, - string name, Size? physicalSize, double? pixelDensity) + public X11Screen( + PixelRect bounds, + bool isPrimary, + string name, + Size? physicalSize, + double? scaling) { - IsPrimary = primary; + IsPrimary = isPrimary; Name = name; Bounds = bounds; - if (physicalSize == null && pixelDensity == null) + if (physicalSize == null && scaling == null) { - PixelDensity = 1; + Scaling = 1; } - else if (pixelDensity == null) + else if (scaling == null) { - PixelDensity = GuessPixelDensity(bounds, physicalSize.Value); + Scaling = GuessPixelDensity(bounds, physicalSize.Value); } else { - PixelDensity = pixelDensity.Value; + Scaling = scaling.Value; PhysicalSize = physicalSize; } } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 75b741ac77..690ac0ebce 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -120,7 +120,7 @@ namespace Avalonia.X11 if (!_popup && Screen != null) { - var monitor = Screen.AllScreens.OrderBy(x => x.Scale) + var monitor = Screen.AllScreens.OrderBy(x => x.Scaling) .FirstOrDefault(m => m.Bounds.Contains(Position)); if (monitor != null) @@ -570,9 +570,9 @@ namespace Avalonia.X11 newScaling = _scalingOverride.Value; else { - var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity) + var monitor = _platform.X11Screens.Screens.OrderBy(x => x.Scaling) .FirstOrDefault(m => m.Bounds.Contains(Position)); - newScaling = monitor?.PixelDensity ?? RenderScaling; + newScaling = monitor?.Scaling ?? RenderScaling; } if (RenderScaling != newScaling) @@ -994,7 +994,7 @@ namespace Avalonia.X11 public IScreenImpl Screen => _platform.Screens; - public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity)) + public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.Scaling)) .OrderByDescending(x => x.Width + x.Height).FirstOrDefault(); diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 93bdfda652..8d565d7fef 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -216,7 +216,7 @@ namespace Avalonia.Win32 { Anchor = PopupAnchor.TopLeft, Gravity = PopupGravity.BottomRight, - AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scale, new Size(1, 1)), + AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scaling, new Size(1, 1)), Size = finalRect.Size, ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY, }); @@ -244,16 +244,16 @@ namespace Avalonia.Win32 { var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft; var size = _hiddenWindow.Screens.Primary.Bounds.Size; - return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scale, size.Height * _hiddenWindow.Screens.Primary.Scale); + return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scaling, size.Height * _hiddenWindow.Screens.Primary.Scaling); } } public void MoveAndResize(Point devicePoint, Size virtualSize) { - _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scale); + _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scaling); } - public double Scaling => _hiddenWindow.Screens.Primary.Scale; + public double Scaling => _hiddenWindow.Screens.Primary.Scaling; } } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index cd8515eaa6..5374614379 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -224,7 +224,7 @@ namespace Avalonia.Win32 } } - private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scale ?? 1; + private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1; public double RenderScaling => _scaling; From 1cf220016f7bd63447b6447f31cd82256d0f548a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 13:31:16 +0100 Subject: [PATCH 048/122] focus the root container on page load. --- src/Web/Avalonia.Web/AvaloniaView.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs index 3a31679424..12d31258b5 100644 --- a/src/Web/Avalonia.Web/AvaloniaView.cs +++ b/src/Web/Avalonia.Web/AvaloniaView.cs @@ -136,6 +136,8 @@ namespace Avalonia.Web DomHelper.ObserveSize(host, null, OnSizeChanged); CanvasHelper.RequestAnimationFrame(_canvas, true); + + InputHelper.FocusElement(_containerElement); } private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args) From 3f4034f0caf06dbdb36438666b391c0a8f62bdd6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 21:41:15 +0100 Subject: [PATCH 049/122] add github sponsors --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index c5a719ce90..df070c35cf 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: avaloniaui open_collective: avalonia From 5d8e4998c1208180395f8c2345c618c12e6bb817 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 22:05:39 +0100 Subject: [PATCH 050/122] prevent browser touch gestures happening outside of avalonia --- src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts index 2257d56a92..783710bb3e 100644 --- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts +++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts @@ -11,6 +11,7 @@ export class AvaloniaDOM { host.tabIndex = 0; host.oncontextmenu = function () { return false; }; host.style.overflow = "hidden"; + host.style.touchAction = "none"; // Rendering target canvas const canvas = document.createElement("canvas"); From 668296b397210192d72c0107135f0cc76f1bc1c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 20 Oct 2022 22:21:49 +0100 Subject: [PATCH 051/122] minify js --- src/Web/Avalonia.Web/webapp/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/webapp/build.js b/src/Web/Avalonia.Web/webapp/build.js index 6b6df4c300..81f863cac7 100644 --- a/src/Web/Avalonia.Web/webapp/build.js +++ b/src/Web/Avalonia.Web/webapp/build.js @@ -5,7 +5,7 @@ require("esbuild").build({ ], outdir: "../wwwroot", bundle: true, - minify: false, + minify: true, format: "esm", target: "es2016", platform: "browser", From 91d25f7416981eebebe769757d944627b2f7ab8c Mon Sep 17 00:00:00 2001 From: robloo Date: Thu, 20 Oct 2022 20:12:40 -0400 Subject: [PATCH 052/122] Fix missed property rename --- samples/ControlCatalog/Pages/ScreenPage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index ab75e4a4e2..ff62b834c4 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -62,7 +62,7 @@ namespace ControlCatalog.Pages CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20)); - formattedText = CreateFormattedText($"Scaling: {screen.Scale * 100}%"); + formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%"); context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40)); formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}"); From 93b77b1b6a2181822f3bd1b074a504366aa4e0b4 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 21 Oct 2022 07:43:36 +0200 Subject: [PATCH 053/122] Move inlines support to TextBlock and rename RichTextBlock to SelectableTextBlock --- .../ControlCatalog/Pages/TextBlockPage.xaml | 4 +- .../Media/TextFormatting/TextFormatterImpl.cs | 4 +- .../Documents/InlineCollection.cs | 9 +- ...ichTextBlock.cs => SelectableTextBlock.cs} | 374 +++--------------- src/Avalonia.Controls/TextBlock.cs | 272 ++++++++++--- .../Controls/FluentControls.xaml | 2 +- .../Controls/RichTextBlock.xaml | 14 - .../Controls/SelectableTextBlock.xaml | 18 + .../Controls/RichTextBlock.xaml | 14 - .../Controls/SelectableTextBlock.xaml | 18 + .../Controls/SimpleControls.xaml | 2 +- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- .../Media/TextShaperImpl.cs | 2 +- .../RichTextBlockTests.cs | 132 ------- .../TextBlockTests.cs | 121 ++++++ 15 files changed, 451 insertions(+), 537 deletions(-) rename src/Avalonia.Controls/{RichTextBlock.cs => SelectableTextBlock.cs} (55%) delete mode 100644 src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml create mode 100644 src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml delete mode 100644 src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml create mode 100644 src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml delete mode 100644 tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index 32914428ed..6bb428e2c7 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -118,7 +118,7 @@ - + This is a TextBlock with several @@ -126,7 +126,7 @@ using a variety of styles . - + diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 73dd3366aa..5df458cc3b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -502,7 +502,7 @@ namespace Avalonia.Media.TextFormatting case { } drawableTextRun: { - if (currentWidth + drawableTextRun.Size.Width > paragraphWidth) + if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth) { goto found; } @@ -665,7 +665,7 @@ namespace Avalonia.Media.TextFormatting if (!breakFound) { - currentLength += currentRun.Text.Length; + currentLength += currentRun.TextSourceLength; continue; } diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 1ba65b3e8f..9ff5627434 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -70,6 +70,11 @@ namespace Avalonia.Controls.Documents { get { + if (Count == 0) + { + return null; + } + var builder = StringBuilderCache.Acquire(); foreach (var inline in this) @@ -111,7 +116,7 @@ namespace Avalonia.Controls.Documents private void AddText(string text) { - if (Parent is RichTextBlock textBlock && !textBlock.HasComplexContent) + if (Parent is TextBlock textBlock && !textBlock.HasComplexContent) { textBlock._text += text; } @@ -123,7 +128,7 @@ namespace Avalonia.Controls.Documents private void OnAdd() { - if (Parent is RichTextBlock textBlock) + if (Parent is TextBlock textBlock) { if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text)) { diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs similarity index 55% rename from src/Avalonia.Controls/RichTextBlock.cs rename to src/Avalonia.Controls/SelectableTextBlock.cs index d0b713ba56..b343439f98 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -8,7 +8,6 @@ using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Media.TextFormatting; -using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Controls @@ -16,67 +15,53 @@ namespace Avalonia.Controls /// /// A control that displays a block of formatted text. /// - public class RichTextBlock : TextBlock, IInlineHost + public class SelectableTextBlock : TextBlock, IInlineHost { - public static readonly StyledProperty IsTextSelectionEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextSelectionEnabled), false); - - public static readonly DirectProperty SelectionStartProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SelectionStartProperty = + AvaloniaProperty.RegisterDirect( nameof(SelectionStart), o => o.SelectionStart, (o, v) => o.SelectionStart = v); - public static readonly DirectProperty SelectionEndProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SelectionEndProperty = + AvaloniaProperty.RegisterDirect( nameof(SelectionEnd), o => o.SelectionEnd, (o, v) => o.SelectionEnd = v); - public static readonly DirectProperty SelectedTextProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SelectedTextProperty = + AvaloniaProperty.RegisterDirect( nameof(SelectedText), o => o.SelectedText); public static readonly StyledProperty SelectionBrushProperty = - AvaloniaProperty.Register(nameof(SelectionBrush), Brushes.Blue); + AvaloniaProperty.Register(nameof(SelectionBrush), Brushes.Blue); - /// - /// Defines the property. - /// - public static readonly StyledProperty InlinesProperty = - AvaloniaProperty.Register( - nameof(Inlines)); - public static readonly DirectProperty CanCopyProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty CanCopyProperty = + AvaloniaProperty.RegisterDirect( nameof(CanCopy), o => o.CanCopy); public static readonly RoutedEvent CopyingToClipboardEvent = - RoutedEvent.Register( + RoutedEvent.Register( nameof(CopyingToClipboard), RoutingStrategies.Bubble); private bool _canCopy; private int _selectionStart; private int _selectionEnd; private int _wordSelectionStart = -1; - private IReadOnlyList? _textRuns; - static RichTextBlock() + static SelectableTextBlock() { - FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true); - - AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty, IsTextSelectionEnabledProperty); + FocusableProperty.OverrideDefaultValue(typeof(SelectableTextBlock), true); + AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty); } - public RichTextBlock() + public event EventHandler? CopyingToClipboard { - Inlines = new InlineCollection - { - Parent = this, - InlineHost = this - }; + add => AddHandler(CopyingToClipboardEvent, value); + remove => RemoveHandler(CopyingToClipboardEvent, value); } /// @@ -99,6 +84,8 @@ namespace Avalonia.Controls if (SetAndRaise(SelectionStartProperty, ref _selectionStart, value)) { RaisePropertyChanged(SelectedTextProperty, "", ""); + + UpdateCommandStates(); } } } @@ -114,6 +101,8 @@ namespace Avalonia.Controls if (SetAndRaise(SelectionEndProperty, ref _selectionEnd, value)) { RaisePropertyChanged(SelectedTextProperty, "", ""); + + UpdateCommandStates(); } } } @@ -126,25 +115,6 @@ namespace Avalonia.Controls get => GetSelection(); } - /// - /// Gets or sets a value that indicates whether text selection is enabled, either through user action or calling selection-related API. - /// - public bool IsTextSelectionEnabled - { - get => GetValue(IsTextSelectionEnabledProperty); - set => SetValue(IsTextSelectionEnabledProperty, value); - } - - /// - /// Gets or sets the inlines. - /// - [Content] - public InlineCollection? Inlines - { - get => GetValue(InlinesProperty); - set => SetValue(InlinesProperty, value); - } - /// /// Property for determining if the Copy command can be executed. /// @@ -154,20 +124,12 @@ namespace Avalonia.Controls private set => SetAndRaise(CanCopyProperty, ref _canCopy, value); } - public event EventHandler? CopyingToClipboard - { - add => AddHandler(CopyingToClipboardEvent, value); - remove => RemoveHandler(CopyingToClipboardEvent, value); - } - - internal bool HasComplexContent => Inlines != null && Inlines.Count > 0; - /// /// Copies the current selection to the Clipboard. /// public async void Copy() { - if (_canCopy || !IsTextSelectionEnabled) + if (!_canCopy) { return; } @@ -188,45 +150,13 @@ namespace Avalonia.Controls await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))) .SetTextAsync(text); } - } - - protected override void RenderTextLayout(DrawingContext context, Point origin) - { - var selectionStart = SelectionStart; - var selectionEnd = SelectionEnd; - var selectionBrush = SelectionBrush; - - var selectionEnabled = IsTextSelectionEnabled; - - if (selectionEnabled && selectionStart != selectionEnd && selectionBrush != null) - { - var start = Math.Min(selectionStart, selectionEnd); - var length = Math.Max(selectionStart, selectionEnd) - start; - - var rects = TextLayout.HitTestTextRange(start, length); - - using (context.PushPostTransform(Matrix.CreateTranslation(origin))) - { - foreach (var rect in rects) - { - context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); - } - } - } - - base.RenderTextLayout(context, origin); - } + } /// /// Select all text in the TextBox /// public void SelectAll() { - if (!IsTextSelectionEnabled) - { - return; - } - var text = Text; SelectionStart = 0; @@ -238,94 +168,52 @@ namespace Avalonia.Controls /// public void ClearSelection() { - if (!IsTextSelectionEnabled) - { - return; - } - SelectionEnd = SelectionStart; } - protected void AddText(string? text) + protected override void OnGotFocus(GotFocusEventArgs e) { - if (string.IsNullOrEmpty(text)) - { - return; - } - - if (!HasComplexContent && string.IsNullOrEmpty(_text)) - { - _text = text; - } - else - { - if (!string.IsNullOrEmpty(_text)) - { - Inlines?.Add(_text); - - _text = null; - } - - Inlines?.Add(text); - } - } + base.OnGotFocus(e); - protected override string? GetText() - { - return _text ?? Inlines?.Text; + UpdateCommandStates(); } - protected override void SetText(string? text) + protected override void OnLostFocus(RoutedEventArgs e) { - var oldValue = GetText(); + base.OnLostFocus(e); - AddText(text); + if ((ContextFlyout == null || !ContextFlyout.IsOpen) && + (ContextMenu == null || !ContextMenu.IsOpen)) + { + ClearSelection(); + } - RaisePropertyChanged(TextProperty, oldValue, text); + UpdateCommandStates(); } - /// - /// Creates the used to render the text. - /// - /// A object. - protected override TextLayout CreateTextLayout(string? text) + protected override void RenderTextLayout(DrawingContext context, Point origin) { - var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); - var defaultProperties = new GenericTextRunProperties( - typeface, - FontSize, - TextDecorations, - Foreground); - - var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, - defaultProperties, TextWrapping, LineHeight, 0); - - ITextSource textSource; + var selectionStart = SelectionStart; + var selectionEnd = SelectionEnd; + var selectionBrush = SelectionBrush; - if (_textRuns != null) + if (selectionStart != selectionEnd && selectionBrush != null) { - textSource = new InlinesTextSource(_textRuns); - } - else - { - textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties); - } + var start = Math.Min(selectionStart, selectionEnd); + var length = Math.Max(selectionStart, selectionEnd) - start; - return new TextLayout( - textSource, - paragraphProperties, - TextTrimming, - _constraint.Width, - _constraint.Height, - maxLines: MaxLines, - lineHeight: LineHeight); - } + var rects = TextLayout.HitTestTextRange(start, length); - protected override void OnLostFocus(RoutedEventArgs e) - { - base.OnLostFocus(e); + using (context.PushPostTransform(Matrix.CreateTranslation(origin))) + { + foreach (var rect in rects) + { + context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); + } + } + } - ClearSelection(); + base.RenderTextLayout(context, origin); } protected override void OnKeyDown(KeyEventArgs e) @@ -352,11 +240,6 @@ namespace Avalonia.Controls { base.OnPointerPressed(e); - if (!IsTextSelectionEnabled) - { - return; - } - var text = Text; var clickInfo = e.GetCurrentPoint(this); @@ -435,11 +318,6 @@ namespace Avalonia.Controls { base.OnPointerMoved(e); - if (!IsTextSelectionEnabled) - { - return; - } - // selection should not change during pointer move if the user right clicks if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { @@ -486,11 +364,6 @@ namespace Avalonia.Controls { base.OnPointerReleased(e); - if (!IsTextSelectionEnabled) - { - return; - } - if (e.Pointer.Captured != this) { return; @@ -521,100 +394,15 @@ namespace Avalonia.Controls e.Pointer.Capture(null); } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - - switch (change.Property.Name) - { - case nameof(Inlines): - { - OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); - InvalidateTextLayout(); - break; - } - } - } - - protected override Size MeasureOverride(Size availableSize) + private void UpdateCommandStates() { - if(_textRuns != null) - { - LogicalChildren.Clear(); - - VisualChildren.Clear(); - - _textRuns = null; - } - - if (Inlines != null && Inlines.Count > 0) - { - var inlines = Inlines; - - var textRuns = new List(); - - foreach (var inline in inlines) - { - inline.BuildTextRun(textRuns); - } - - foreach (var textRun in textRuns) - { - if (textRun is EmbeddedControlRun controlRun && - controlRun.Control is Control control) - { - LogicalChildren.Add(control); - - VisualChildren.Add(control); - - control.Measure(Size.Infinity); - } - } - - _textRuns = textRuns; - } - - return base.MeasureOverride(availableSize); - } - - protected override Size ArrangeOverride(Size finalSize) - { - if (HasComplexContent) - { - var currentY = 0.0; - - foreach (var textLine in TextLayout.TextLines) - { - var currentX = textLine.Start; - - foreach (var run in textLine.TextRuns) - { - if (run is DrawableTextRun drawable) - { - if (drawable is EmbeddedControlRun controlRun - && controlRun.Control is Control control) - { - control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); - } - - currentX += drawable.Size.Width; - } - } + var text = GetSelection(); - currentY += textLine.Height; - } - } - - return base.ArrangeOverride(finalSize); + CanCopy = !string.IsNullOrEmpty(text); } private string GetSelection() { - if (!IsTextSelectionEnabled) - { - return ""; - } - var text = GetText(); if (string.IsNullOrEmpty(text)) @@ -638,59 +426,5 @@ namespace Avalonia.Controls return selectedText; } - - private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) - { - if (oldValue is not null) - { - oldValue.Parent = null; - oldValue.InlineHost = null; - oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); - } - - if (newValue is not null) - { - newValue.Parent = this; - newValue.InlineHost = this; - newValue.Invalidated += (s, e) => InvalidateTextLayout(); - } - } - - void IInlineHost.Invalidate() - { - InvalidateTextLayout(); - } - - private readonly struct InlinesTextSource : ITextSource - { - private readonly IReadOnlyList _textRuns; - - public InlinesTextSource(IReadOnlyList textRuns) - { - _textRuns = textRuns; - } - - public TextRun? GetTextRun(int textSourceIndex) - { - var currentPosition = 0; - - foreach (var textRun in _textRuns) - { - if (textRun.TextSourceLength == 0) - { - continue; - } - - if (currentPosition >= textSourceIndex) - { - return textRun; - } - - currentPosition += textRun.TextSourceLength; - } - - return null; - } - } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 99c8068b3d..7d4d326a0c 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Automation.Peers; using Avalonia.Controls.Documents; using Avalonia.Layout; @@ -12,7 +13,7 @@ namespace Avalonia.Controls /// /// A control that displays a block of text. /// - public class TextBlock : Control, IAddChild + public class TextBlock : Control, IInlineHost { /// /// Defines the property. @@ -96,15 +97,15 @@ namespace Avalonia.Controls public static readonly DirectProperty TextProperty = AvaloniaProperty.RegisterDirect( nameof(Text), - o => o.Text, - (o, v) => o.Text = v); + o => o.GetText(), + (o, v) => o.SetText(v)); /// /// Defines the property. /// public static readonly AttachedProperty TextAlignmentProperty = AvaloniaProperty.RegisterAttached( - nameof(TextAlignment), + nameof(TextAlignment), defaultValue: TextAlignment.Start, inherits: true); @@ -112,14 +113,14 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly AttachedProperty TextWrappingProperty = - AvaloniaProperty.RegisterAttached(nameof(TextWrapping), + AvaloniaProperty.RegisterAttached(nameof(TextWrapping), inherits: true); /// /// Defines the property. /// public static readonly AttachedProperty TextTrimmingProperty = - AvaloniaProperty.RegisterAttached(nameof(TextTrimming), + AvaloniaProperty.RegisterAttached(nameof(TextTrimming), defaultValue: TextTrimming.None, inherits: true); @@ -129,9 +130,17 @@ namespace Avalonia.Controls public static readonly StyledProperty TextDecorationsProperty = AvaloniaProperty.Register(nameof(TextDecorations)); + /// + /// Defines the property. + /// + public static readonly StyledProperty InlinesProperty = + AvaloniaProperty.Register( + nameof(Inlines)); + internal string? _text; protected TextLayout? _textLayout; protected Size _constraint; + private IReadOnlyList? _textRuns; /// /// Initializes static members of the class. @@ -139,10 +148,19 @@ namespace Avalonia.Controls static TextBlock() { ClipToBoundsProperty.OverrideDefaultValue(true); - + AffectsRender(BackgroundProperty, ForegroundProperty); } + public TextBlock() + { + Inlines = new InlineCollection + { + Parent = this, + InlineHost = this + }; + } + /// /// Gets the used to render the text. /// @@ -288,9 +306,21 @@ namespace Avalonia.Controls get => GetValue(TextDecorationsProperty); set => SetValue(TextDecorationsProperty, value); } - + + /// + /// Gets or sets the inlines. + /// + [Content] + public InlineCollection? Inlines + { + get => GetValue(InlinesProperty); + set => SetValue(InlinesProperty, value); + } + protected override bool BypassFlowDirectionPolicies => true; + internal bool HasComplexContent => Inlines != null && Inlines.Count > 0; + /// /// The BaselineOffset property provides an adjustment to baseline offset /// @@ -513,19 +543,30 @@ namespace Avalonia.Controls TextLayout.Draw(context, origin); } - void IAddChild.AddChild(string text) - { - _text = text; - } - protected virtual string? GetText() { - return _text; + return _text ?? Inlines?.Text; } protected virtual void SetText(string? text) { - SetAndRaise(TextProperty, ref _text, text); + if (Inlines != null && Inlines.Count > 0) + { + var oldValue = Inlines.Text; + + if (!string.IsNullOrEmpty(text)) + { + Inlines.Add(text); + } + + text = Inlines.Text; + + RaisePropertyChanged(TextProperty, oldValue, text); + } + else + { + SetAndRaise(TextProperty, ref _text, text); + } } /// @@ -534,8 +575,10 @@ namespace Avalonia.Controls /// A object. protected virtual TextLayout CreateTextLayout(string? text) { + var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); + var defaultProperties = new GenericTextRunProperties( - new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), + typeface, FontSize, TextDecorations, Foreground); @@ -543,8 +586,19 @@ namespace Avalonia.Controls var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, defaultProperties, TextWrapping, LineHeight, 0); + ITextSource textSource; + + if (_textRuns != null) + { + textSource = new InlinesTextSource(_textRuns); + } + else + { + textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties); + } + return new TextLayout( - new SimpleTextSource((text ?? "").AsMemory(), defaultProperties), + textSource, paragraphProperties, TextTrimming, _constraint.Width, @@ -560,6 +614,8 @@ namespace Avalonia.Controls { _textLayout = null; + InvalidateVisual(); + InvalidateMeasure(); } @@ -573,7 +629,39 @@ namespace Avalonia.Controls _textLayout = null; - InvalidateArrange(); + var inlines = Inlines; + + if (HasComplexContent) + { + if (_textRuns != null) + { + LogicalChildren.Clear(); + + VisualChildren.Clear(); + } + + var textRuns = new List(); + + foreach (var inline in inlines!) + { + inline.BuildTextRun(textRuns); + } + + foreach (var textRun in textRuns) + { + if (textRun is EmbeddedControlRun controlRun && + controlRun.Control is Control control) + { + VisualChildren.Add(control); + + LogicalChildren.Add(control); + + control.Measure(Size.Infinity); + } + } + + _textRuns = textRuns; + } var measuredSize = TextLayout.Bounds.Size.Inflate(padding); @@ -584,16 +672,11 @@ namespace Avalonia.Controls { var textWidth = Math.Ceiling(TextLayout.Bounds.Width); - if(finalSize.Width < textWidth) + if (finalSize.Width < textWidth) { finalSize = finalSize.WithWidth(textWidth); } - if (MathUtilities.AreClose(_constraint.Width, finalSize.Width)) - { - return finalSize; - } - var scale = LayoutHelper.GetLayoutScale(this); var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); @@ -602,6 +685,32 @@ namespace Avalonia.Controls _textLayout = null; + if (HasComplexContent) + { + var currentY = padding.Top; + + foreach (var textLine in TextLayout.TextLines) + { + var currentX = padding.Left + textLine.Start; + + foreach (var run in textLine.TextRuns) + { + if (run is DrawableTextRun drawable) + { + if (drawable is EmbeddedControlRun controlRun + && controlRun.Control is Control control) + { + control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); + } + + currentX += drawable.Size.Width; + } + } + + currentY += textLine.Height; + } + } + return finalSize; } @@ -610,42 +719,70 @@ namespace Avalonia.Controls return new TextBlockAutomationPeer(this); } - private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; - - private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0; - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); switch (change.Property.Name) { - case nameof (FontSize): - case nameof (FontWeight): - case nameof (FontStyle): - case nameof (FontFamily): - case nameof (FontStretch): + case nameof(FontSize): + case nameof(FontWeight): + case nameof(FontStyle): + case nameof(FontFamily): + case nameof(FontStretch): + + case nameof(TextWrapping): + case nameof(TextTrimming): + case nameof(TextAlignment): + + case nameof(FlowDirection): + + case nameof(Padding): + case nameof(LineHeight): + case nameof(MaxLines): + + case nameof(Text): + case nameof(TextDecorations): + case nameof(Foreground): + { + InvalidateTextLayout(); + break; + } + case nameof(Inlines): + { + OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); + InvalidateTextLayout(); + break; + } + } + } - case nameof (TextWrapping): - case nameof (TextTrimming): - case nameof (TextAlignment): + private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; - case nameof (FlowDirection): + private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0; - case nameof (Padding): - case nameof (LineHeight): - case nameof (MaxLines): + private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) + { + if (oldValue is not null) + { + oldValue.Parent = null; + oldValue.InlineHost = null; + oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); + } - case nameof (Text): - case nameof (TextDecorations): - case nameof (Foreground): - { - InvalidateTextLayout(); - break; - } + if (newValue is not null) + { + newValue.Parent = this; + newValue.InlineHost = this; + newValue.Invalidated += (s, e) => InvalidateTextLayout(); } } + void IInlineHost.Invalidate() + { + InvalidateTextLayout(); + } + protected readonly struct SimpleTextSource : ITextSource { private readonly ReadOnlySlice _text; @@ -674,5 +811,46 @@ namespace Avalonia.Controls return new TextCharacters(runText, _defaultProperties); } } + + private readonly struct InlinesTextSource : ITextSource + { + private readonly IReadOnlyList _textRuns; + + public InlinesTextSource(IReadOnlyList textRuns) + { + _textRuns = textRuns; + } + + public IReadOnlyList TextRuns => _textRuns; + + public TextRun? GetTextRun(int textSourceIndex) + { + var currentPosition = 0; + + foreach (var textRun in _textRuns) + { + if (textRun.TextSourceLength == 0) + { + continue; + } + + if (textSourceIndex >= currentPosition + textRun.TextSourceLength) + { + currentPosition += textRun.TextSourceLength; + + continue; + } + + if (textRun is TextCharacters textCharacters) + { + return new TextCharacters(textRun.Text.Skip(Math.Max(0, textSourceIndex - currentPosition)), textRun.Properties!); + } + + return textRun; + } + + return null; + } + } } } diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 577539b26b..5383aa3180 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -68,7 +68,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml deleted file mode 100644 index 75af2efcb1..0000000000 --- a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml new file mode 100644 index 0000000000..f630969ae6 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml deleted file mode 100644 index c0570282cb..0000000000 --- a/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml b/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml new file mode 100644 index 0000000000..aaa6448aea --- /dev/null +++ b/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml index 644c6ed416..4aefa0136c 100644 --- a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml +++ b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml @@ -64,7 +64,7 @@ - + diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index b07deb1f4d..d6bb37a06a 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -64,7 +64,7 @@ namespace Avalonia.Skia var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); - if(glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t') + if(text.Buffer.Span[glyphCluster] == '\t') { glyphIndex = typeface.GetGlyph(' '); diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs index 064320f809..7f2cbc6182 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs @@ -64,7 +64,7 @@ namespace Avalonia.Direct2D1.Media var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); - if (glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t') + if (text.Buffer.Span[glyphCluster] == '\t') { glyphIndex = typeface.GetGlyph(' '); diff --git a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs deleted file mode 100644 index 05007e4f2e..0000000000 --- a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Avalonia.Controls.Documents; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Media; -using Avalonia.UnitTests; -using Xunit; - -namespace Avalonia.Controls.UnitTests -{ - public class RichTextBlockTests - { - [Fact] - public void Changing_InlinesCollection_Should_Invalidate_Measure() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - target.Inlines.Add(new Run("Hello")); - - Assert.False(target.IsMeasureValid); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - } - } - - [Fact] - public void Changing_Inlines_Properties_Should_Invalidate_Measure() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - var inline = new Run("Hello"); - - target.Inlines.Add(inline); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - inline.Foreground = Brushes.Green; - - Assert.False(target.IsMeasureValid); - } - } - - [Fact] - public void Changing_Inlines_Should_Invalidate_Measure() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - var inlines = new InlineCollection { new Run("Hello") }; - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - target.Inlines = inlines; - - Assert.False(target.IsMeasureValid); - } - } - - [Fact] - public void Changing_Inlines_Should_Reset_Inlines_Parent() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - var run = new Run("Hello"); - - target.Inlines.Add(run); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - target.Inlines = null; - - Assert.Null(run.Parent); - - target.Inlines = new InlineCollection { run }; - - Assert.Equal(target, run.Parent); - } - } - - [Fact] - public void InlineUIContainer_Child_Schould_Be_Arranged() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var target = new RichTextBlock(); - - var button = new Button { Content = "12345678" }; - - button.Template = new FuncControlTemplate public static readonly RoutedEvent SizeChangedEvent = RoutedEvent.Register( - nameof(SizeChanged), RoutingStrategies.Bubble); + nameof(SizeChanged), RoutingStrategies.Direct); /// /// Defines the property. From 62485f53bc26d5f80795799b36df80b445c60d9f Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 30 Oct 2022 22:10:58 -0400 Subject: [PATCH 094/122] Only raise SizeChanged when the Size component changes (ignore position) --- src/Avalonia.Controls/Control.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index e21f0fd33d..beaee34b07 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -556,13 +556,20 @@ namespace Avalonia.Controls var oldValue = change.GetOldValue(); var newValue = change.GetNewValue(); - var sizeChangedEventArgs = new SizeChangedEventArgs( - SizeChangedEvent, - source: this, - previousSize: new Size(oldValue.Width, oldValue.Height), - newSize: new Size(newValue.Width, newValue.Height)); + // Bounds is a Rect with an X/Y Position as well as Height/Width. + // This means it is possible for the Rect to change position but not size. + // Therefore, we want to explicity check only the size and raise an event + // only when that size has changed. + if (newValue.Size != oldValue.Size) + { + var sizeChangedEventArgs = new SizeChangedEventArgs( + SizeChangedEvent, + source: this, + previousSize: new Size(oldValue.Width, oldValue.Height), + newSize: new Size(newValue.Width, newValue.Height)); - RaiseEvent(sizeChangedEventArgs); + RaiseEvent(sizeChangedEventArgs); + } } else if (change.Property == FlowDirectionProperty) { From bdd637298e7822aa3166c9097c99fd9fd49a91ef Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 31 Oct 2022 20:08:50 -0400 Subject: [PATCH 095/122] Take into account LayoutEpsilon when calculating Height/WidthChanged --- .../Interactivity/RoutedEventArgs.cs | 2 +- src/Avalonia.Controls/SizeChangedEventArgs.cs | 29 +++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs index 60a6b64677..2b660e7080 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs @@ -3,7 +3,7 @@ using System; namespace Avalonia.Interactivity { /// - /// Provices state information and data specific to a routed event. + /// Provides state information and data specific to a routed event. /// public class RoutedEventArgs : EventArgs { diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs index 201a00a3fc..b3e399ff55 100644 --- a/src/Avalonia.Controls/SizeChangedEventArgs.cs +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -1,4 +1,6 @@ using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -42,14 +44,23 @@ namespace Avalonia.Controls { PreviousSize = previousSize; NewSize = newSize; - HeightChanged = newSize.Height != previousSize.Height; - WidthChanged = newSize.Width != previousSize.Width; + + // Only consider changed when the size difference is greater than LayoutEpsilon + // This compensates for any rounding or precision difference between layout cycles + HeightChanged = !MathUtilities.AreClose(newSize.Height, previousSize.Height, LayoutHelper.LayoutEpsilon); + WidthChanged = !MathUtilities.AreClose(newSize.Width, previousSize.Width, LayoutHelper.LayoutEpsilon); } /// - /// Gets a value indicating whether the height of the new size is different - /// than the previous size height. + /// Gets a value indicating whether the height of the new size is considered + /// different than the previous size height. /// + /// + /// This will take into account layout epsilon and will not be true if both + /// heights are considered equivalent for layout purposes. Remember there can + /// be small variations in the calculations between layout cycles due to + /// rounding and precision even when the size has not otherwise changed. + /// public bool HeightChanged { get; init; } /// @@ -63,9 +74,15 @@ namespace Avalonia.Controls public Size PreviousSize { get; init; } /// - /// Gets a value indicating whether the width of the new size is different - /// than the previous size width. + /// Gets a value indicating whether the width of the new size is considered + /// different than the previous size width. /// + /// + /// This will take into account layout epsilon and will not be true if both + /// heights are considered equivalent for layout purposes. Remember there can + /// be small variations in the calculations between layout cycles due to + /// rounding and precision even when the size has not otherwise changed. + /// public bool WidthChanged { get; init; } } } From 3e53621daee8b07be3e295e5e12931bdec658dc4 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 31 Oct 2022 20:12:21 -0400 Subject: [PATCH 096/122] Fix typo --- src/Avalonia.Controls/SizeChangedEventArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs index b3e399ff55..fd40c50505 100644 --- a/src/Avalonia.Controls/SizeChangedEventArgs.cs +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -79,7 +79,7 @@ namespace Avalonia.Controls /// /// /// This will take into account layout epsilon and will not be true if both - /// heights are considered equivalent for layout purposes. Remember there can + /// widths are considered equivalent for layout purposes. Remember there can /// be small variations in the calculations between layout cycles due to /// rounding and precision even when the size has not otherwise changed. /// From 9403a3d0c7050a1553c0b5a0958304570453ed9b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 1 Nov 2022 09:07:42 +0100 Subject: [PATCH 097/122] Fix TextBlock inlines logical tree handling --- .../Documents/InlineCollection.cs | 25 ++++++++++--------- src/Avalonia.Controls/Documents/Span.cs | 4 +-- src/Avalonia.Controls/TextBlock.cs | 17 +++++++++---- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 9ff5627434..54ee9c99d5 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Documents [WhitespaceSignificantCollection] public class InlineCollection : AvaloniaList { - private ILogical? _parent; + private IAvaloniaList? _parent; private IInlineHost? _inlineHost; /// @@ -24,28 +24,28 @@ namespace Avalonia.Controls.Documents this.ForEachItem( x => - { - ((ISetLogicalParent)x).SetParent(Parent); + { x.InlineHost = InlineHost; + Parent?.Add(x); Invalidate(); }, x => { - ((ISetLogicalParent)x).SetParent(null); + Parent?.Remove(x); x.InlineHost = InlineHost; Invalidate(); }, () => throw new NotSupportedException()); } - internal ILogical? Parent + internal IAvaloniaList? Parent { get => _parent; set { _parent = value; - OnParentChanged(value); + OnParentChanged(_parent, value); } } @@ -157,20 +157,21 @@ namespace Avalonia.Controls.Documents Invalidated?.Invoke(this, EventArgs.Empty); } - private void OnParentChanged(ILogical? parent) + private void OnParentChanged(IAvaloniaList? oldParent, IAvaloniaList? newParent) { foreach (var child in this) { - var oldParent = child.Parent; - - if (oldParent != parent) + if (oldParent != newParent) { if (oldParent != null) { - ((ISetLogicalParent)child).SetParent(null); + oldParent.Remove(child); } - ((ISetLogicalParent)child).SetParent(parent); + if(newParent != null) + { + newParent.Add(child); + } } } } diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index 363ce1011b..041cdc74ce 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.Documents { Inlines = new InlineCollection { - Parent = this + Parent = LogicalChildren }; } @@ -85,7 +85,7 @@ namespace Avalonia.Controls.Documents if (newValue is not null) { - newValue.Parent = this; + newValue.Parent = LogicalChildren; newValue.InlineHost = InlineHost; newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 7d4d326a0c..75a275f778 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -156,7 +156,7 @@ namespace Avalonia.Controls { Inlines = new InlineCollection { - Parent = this, + Parent = LogicalChildren, InlineHost = this }; } @@ -635,9 +635,16 @@ namespace Avalonia.Controls { if (_textRuns != null) { - LogicalChildren.Clear(); + foreach (var textRun in _textRuns) + { + if (textRun is EmbeddedControlRun controlRun && + controlRun.Control is Control control) + { + VisualChildren.Remove(control); - VisualChildren.Clear(); + LogicalChildren.Remove(control); + } + } } var textRuns = new List(); @@ -772,7 +779,7 @@ namespace Avalonia.Controls if (newValue is not null) { - newValue.Parent = this; + newValue.Parent = LogicalChildren; newValue.InlineHost = this; newValue.Invalidated += (s, e) => InvalidateTextLayout(); } @@ -841,7 +848,7 @@ namespace Avalonia.Controls continue; } - if (textRun is TextCharacters textCharacters) + if (textRun is TextCharacters) { return new TextCharacters(textRun.Text.Skip(Math.Max(0, textSourceIndex - currentPosition)), textRun.Properties!); } From e639aead67cd95e96709e8153aee75e5d4f3ab83 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 1 Nov 2022 10:36:31 +0100 Subject: [PATCH 098/122] Fix logical parent cleanup --- .../Documents/InlineCollection.cs | 20 ++++++++++--------- src/Avalonia.Controls/Documents/Span.cs | 6 +++--- src/Avalonia.Controls/TextBlock.cs | 6 +++--- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 54ee9c99d5..a265f88e21 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Documents [WhitespaceSignificantCollection] public class InlineCollection : AvaloniaList { - private IAvaloniaList? _parent; + private IAvaloniaList? _logicalChildren; private IInlineHost? _inlineHost; /// @@ -26,26 +26,28 @@ namespace Avalonia.Controls.Documents x => { x.InlineHost = InlineHost; - Parent?.Add(x); + LogicalChildren?.Add(x); Invalidate(); }, x => { - Parent?.Remove(x); + LogicalChildren?.Remove(x); x.InlineHost = InlineHost; Invalidate(); }, () => throw new NotSupportedException()); } - internal IAvaloniaList? Parent + internal IAvaloniaList? LogicalChildren { - get => _parent; + get => _logicalChildren; set { - _parent = value; + var oldValue = _logicalChildren; - OnParentChanged(_parent, value); + _logicalChildren = value; + + OnParentChanged(oldValue, value); } } @@ -116,7 +118,7 @@ namespace Avalonia.Controls.Documents private void AddText(string text) { - if (Parent is TextBlock textBlock && !textBlock.HasComplexContent) + if (LogicalChildren is TextBlock textBlock && !textBlock.HasComplexContent) { textBlock._text += text; } @@ -128,7 +130,7 @@ namespace Avalonia.Controls.Documents private void OnAdd() { - if (Parent is TextBlock textBlock) + if (LogicalChildren is TextBlock textBlock) { if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text)) { diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index 041cdc74ce..a7a702ceae 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.Documents { Inlines = new InlineCollection { - Parent = LogicalChildren + LogicalChildren = LogicalChildren }; } @@ -78,14 +78,14 @@ namespace Avalonia.Controls.Documents { if (oldValue is not null) { - oldValue.Parent = null; + oldValue.LogicalChildren = null; oldValue.InlineHost = null; oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate(); } if (newValue is not null) { - newValue.Parent = LogicalChildren; + newValue.LogicalChildren = LogicalChildren; newValue.InlineHost = InlineHost; newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 75a275f778..f79d3f8296 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -156,7 +156,7 @@ namespace Avalonia.Controls { Inlines = new InlineCollection { - Parent = LogicalChildren, + LogicalChildren = LogicalChildren, InlineHost = this }; } @@ -772,14 +772,14 @@ namespace Avalonia.Controls { if (oldValue is not null) { - oldValue.Parent = null; + oldValue.LogicalChildren = null; oldValue.InlineHost = null; oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); } if (newValue is not null) { - newValue.Parent = LogicalChildren; + newValue.LogicalChildren = LogicalChildren; newValue.InlineHost = this; newValue.Invalidated += (s, e) => InvalidateTextLayout(); } From 66595bad2e9b532a434b6470eb67c2f32f718527 Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 1 Nov 2022 07:45:03 -0400 Subject: [PATCH 099/122] Calculate Height/WidthChanged in the getter directly --- src/Avalonia.Controls/SizeChangedEventArgs.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs index fd40c50505..2dc642b163 100644 --- a/src/Avalonia.Controls/SizeChangedEventArgs.cs +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -44,11 +44,6 @@ namespace Avalonia.Controls { PreviousSize = previousSize; NewSize = newSize; - - // Only consider changed when the size difference is greater than LayoutEpsilon - // This compensates for any rounding or precision difference between layout cycles - HeightChanged = !MathUtilities.AreClose(newSize.Height, previousSize.Height, LayoutHelper.LayoutEpsilon); - WidthChanged = !MathUtilities.AreClose(newSize.Width, previousSize.Width, LayoutHelper.LayoutEpsilon); } /// @@ -61,7 +56,7 @@ namespace Avalonia.Controls /// be small variations in the calculations between layout cycles due to /// rounding and precision even when the size has not otherwise changed. /// - public bool HeightChanged { get; init; } + public bool HeightChanged => !MathUtilities.AreClose(NewSize.Height, PreviousSize.Height, LayoutHelper.LayoutEpsilon); /// /// Gets the new size (or bounds) of the object. @@ -83,6 +78,6 @@ namespace Avalonia.Controls /// be small variations in the calculations between layout cycles due to /// rounding and precision even when the size has not otherwise changed. /// - public bool WidthChanged { get; init; } + public bool WidthChanged => !MathUtilities.AreClose(NewSize.Width, PreviousSize.Width, LayoutHelper.LayoutEpsilon); } } From b13f7cbd38163742c8e2972de4c3aa400b43fac7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 2 Nov 2022 15:47:18 +0600 Subject: [PATCH 100/122] Use strong-named version of microcom --- nukebuild/_build.csproj | 2 +- src/Avalonia.MicroCom/Avalonia.MicroCom.csproj | 2 +- src/Avalonia.Native/Avalonia.Native.csproj | 2 +- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 8c0d824298..865d935ad7 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index e784bda105..14dafb7284 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -4,7 +4,7 @@ true - + false all diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 4ceb1be340..c924ef3a45 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 0d29bb91ea..308e22b4e2 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -11,7 +11,7 @@ - + From 17b2834d21f7e7acd63586b69f8ece4d7099f131 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 2 Nov 2022 10:56:06 +0100 Subject: [PATCH 101/122] Implement letter spacing --- src/Avalonia.Base/Media/FormattedText.cs | 3 +- src/Avalonia.Base/Media/GlyphRun.cs | 77 +--------- .../GenericTextParagraphProperties.cs | 19 ++- .../Media/TextFormatting/TextFormatterImpl.cs | 3 +- .../Media/TextFormatting/TextLayout.cs | 21 ++- .../TextFormatting/TextParagraphProperties.cs | 9 +- .../Media/TextFormatting/TextShaperOptions.cs | 9 +- .../Platform/IPlatformRenderInterface.cs | 37 +---- .../Presenters/TextPresenter.cs | 20 ++- src/Avalonia.Controls/TextBlock.cs | 56 +++++++- src/Avalonia.Controls/TextBox.cs | 12 ++ .../HeadlessPlatformRenderInterface.cs | 42 +----- .../Controls/TextBox.xaml | 1 + .../Controls/TextBox.xaml | 4 +- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 4 - .../Avalonia.Skia/PlatformRenderInterface.cs | 126 +++++++++-------- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 131 +++++++++--------- .../VisualTree/MockRenderInterface.cs | 17 +-- .../NullRenderingPlatform.cs | 15 +- .../TextFormatting/TextFormatterTests.cs | 2 +- .../Media/TextFormatting/TextLineTests.cs | 6 +- tests/Avalonia.UnitTests/MockGlyphRun.cs | 12 ++ .../MockPlatformRenderInterface.cs | 2 +- 24 files changed, 306 insertions(+), 324 deletions(-) create mode 100644 tests/Avalonia.UnitTests/MockGlyphRun.cs diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index 27d99bdc10..90b9755493 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -93,7 +93,8 @@ namespace Avalonia.Media runProps, TextWrapping.WrapWithOverflow, 0, // line height not specified - 0 // indentation not specified + 0, // indentation not specified + 0 ); InvalidateMetrics(); diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index a1cb00e209..d93a68e78b 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -170,7 +170,7 @@ namespace Avalonia.Media } /// - /// Gets the scale of the current + /// Gets the scale of the current /// internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight; @@ -860,82 +860,9 @@ namespace Avalonia.Media private IGlyphRunImpl CreateGlyphRunImpl() { - IGlyphRunImpl glyphRunImpl; - var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); - var count = GlyphIndices.Count; - var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight); - - if (GlyphOffsets == null) - { - if (GlyphTypeface.Metrics.IsFixedPitch) - { - var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); - - var glyphs = buffer.GlyphIndices; - - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = GlyphIndices[i]; - } - - glyphRunImpl = buffer.Build(); - } - else - { - var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); - var glyphs = buffer.GlyphIndices; - var positions = buffer.GlyphPositions; - var width = 0d; - - for (var i = 0; i < count; i++) - { - positions[i] = (float)width; - - if (GlyphAdvances == null) - { - width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale; - } - else - { - width += GlyphAdvances[i]; - } - - glyphs[i] = GlyphIndices[i]; - } - - glyphRunImpl = buffer.Build(); - } - } - else - { - var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); - var glyphs = buffer.GlyphIndices; - var glyphPositions = buffer.GlyphPositions; - var currentX = 0.0; - - for (var i = 0; i < count; i++) - { - var glyphOffset = GlyphOffsets[i]; - - glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y); - - if (GlyphAdvances == null) - { - currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale; - } - else - { - currentX += GlyphAdvances[i]; - } - - glyphs[i] = GlyphIndices[i]; - } - - glyphRunImpl = buffer.Build(); - } - return glyphRunImpl; + return platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets); } void IDisposable.Dispose() diff --git a/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs b/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs index dccad1e647..b9ed31523e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs @@ -17,15 +17,18 @@ /// logical horizontal alignment /// text wrap option /// Paragraph line height + /// letter spacing public GenericTextParagraphProperties(TextRunProperties defaultTextRunProperties, TextAlignment textAlignment = TextAlignment.Left, TextWrapping textWrap = TextWrapping.NoWrap, - double lineHeight = 0) + double lineHeight = 0, + double letterSpacing = 0) { DefaultTextRunProperties = defaultTextRunProperties; _textAlignment = textAlignment; _textWrap = textWrap; _lineHeight = lineHeight; + LetterSpacing = letterSpacing; } /// @@ -39,6 +42,7 @@ /// text wrap option /// Paragraph line height /// line indentation + /// letter spacing public GenericTextParagraphProperties( FlowDirection flowDirection, TextAlignment textAlignment, @@ -47,8 +51,8 @@ TextRunProperties defaultTextRunProperties, TextWrapping textWrap, double lineHeight, - double indent - ) + double indent, + double letterSpacing) { _flowDirection = flowDirection; _textAlignment = textAlignment; @@ -57,6 +61,7 @@ DefaultTextRunProperties = defaultTextRunProperties; _textWrap = textWrap; _lineHeight = lineHeight; + LetterSpacing = letterSpacing; Indent = indent; } @@ -72,7 +77,8 @@ textParagraphProperties.DefaultTextRunProperties, textParagraphProperties.TextWrapping, textParagraphProperties.LineHeight, - textParagraphProperties.Indent) + textParagraphProperties.Indent, + textParagraphProperties.LetterSpacing) { } @@ -131,6 +137,11 @@ /// public override double Indent { get; } + /// + /// The letter spacing + /// + public override double LetterSpacing { get; } + /// /// Set text flow direction /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 145c99cadc..7bad95c4a2 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -249,7 +249,8 @@ namespace Avalonia.Media.TextFormatting var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface, currentRun.Properties.FontRenderingEmSize, - shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab); + shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, + paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); drawableTextRuns.AddRange(ShapeTogether(groupedRuns, text, shaperOptions)); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 0828b6518a..dc79e61333 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -31,6 +31,7 @@ namespace Avalonia.Media.TextFormatting /// The maximum width. /// The maximum height. /// The height of each line of text. + /// The letter spacing that is applied to rendered glyphs. /// The maximum number of text lines. /// The text style overrides. public TextLayout( @@ -46,12 +47,13 @@ namespace Avalonia.Media.TextFormatting double maxWidth = double.PositiveInfinity, double maxHeight = double.PositiveInfinity, double lineHeight = double.NaN, + double letterSpacing = 0, int maxLines = 0, IReadOnlyList>? textStyleOverrides = null) { _paragraphProperties = CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, - textDecorations, flowDirection, lineHeight); + textDecorations, flowDirection, lineHeight, letterSpacing); _textSource = new FormattedTextSource(text.AsMemory(), _paragraphProperties.DefaultTextRunProperties, textStyleOverrides); @@ -63,6 +65,8 @@ namespace Avalonia.Media.TextFormatting MaxHeight = maxHeight; + LetterSpacing = letterSpacing; + MaxLines = maxLines; TextLines = CreateTextLines(); @@ -77,6 +81,7 @@ namespace Avalonia.Media.TextFormatting /// The maximum width. /// The maximum height. /// The height of each line of text. + /// The letter spacing that is applied to rendered glyphs. /// The maximum number of text lines. public TextLayout( ITextSource textSource, @@ -85,6 +90,7 @@ namespace Avalonia.Media.TextFormatting double maxWidth = double.PositiveInfinity, double maxHeight = double.PositiveInfinity, double lineHeight = double.NaN, + double letterSpacing = 0, int maxLines = 0) { _textSource = textSource; @@ -99,6 +105,8 @@ namespace Avalonia.Media.TextFormatting MaxHeight = maxHeight; + LetterSpacing = letterSpacing; + MaxLines = maxLines; TextLines = CreateTextLines(); @@ -128,6 +136,11 @@ namespace Avalonia.Media.TextFormatting /// public int MaxLines { get; } + /// + /// Gets the text spacing. + /// + public double LetterSpacing { get; } + /// /// Gets the text lines. /// @@ -374,15 +387,17 @@ namespace Avalonia.Media.TextFormatting /// The text decorations. /// The text flow direction. /// The height of each line of text. + /// The letter spacing that is applied to rendered glyphs. /// private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping, - TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight) + TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight, + double letterSpacing) { var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground); return new GenericTextParagraphProperties(flowDirection, textAlignment, true, false, - textRunStyle, textWrapping, lineHeight, 0); + textRunStyle, textWrapping, lineHeight, 0, letterSpacing); } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs index 82a0ba14d8..5691dd8ad0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs @@ -57,7 +57,7 @@ public abstract double Indent { get; } /// - /// Paragraph indentation + /// Get the paragraph indentation. /// public virtual double ParagraphIndent { @@ -65,11 +65,16 @@ } /// - /// Default Incremental Tab + /// Gets the default incremental tab width. /// public virtual double DefaultIncrementalTab { get { return 4 * DefaultTextRunProperties.FontRenderingEmSize; } } + + /// + /// Gets the letter spacing. + /// + public virtual double LetterSpacing { get; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index 0d00bed51e..80bbbcdbfe 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs @@ -12,13 +12,15 @@ namespace Avalonia.Media.TextFormatting double fontRenderingEmSize = 12, sbyte bidiLevel = 0, CultureInfo? culture = null, - double incrementalTabWidth = 0) + double incrementalTabWidth = 0, + double letterSpacing = 0) { Typeface = typeface; FontRenderingEmSize = fontRenderingEmSize; BidiLevel = bidiLevel; Culture = culture; IncrementalTabWidth = incrementalTabWidth; + LetterSpacing = letterSpacing; } /// @@ -45,5 +47,10 @@ namespace Avalonia.Media.TextFormatting /// public double IncrementalTabWidth { get; } + /// + /// Get the letter spacing. + /// + public double LetterSpacing { get; } + } } diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 9d0d7974b4..518c5f37b8 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -171,40 +171,15 @@ namespace Avalonia.Platform IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); /// - /// Allocates a platform glyph run buffer. + /// Creates a platform implementation of a glyph run. /// /// The glyph typeface. /// The font rendering em size. - /// The length. - /// An . - /// - /// This buffer only holds glyph indices. - /// - IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); - - /// - /// Allocates a horizontal platform glyph run buffer. - /// - /// The glyph typeface. - /// The font rendering em size. - /// The length. - /// An . - /// - /// This buffer holds glyph indices and glyph advances. - /// - IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); - - /// - /// Allocates a positioned platform glyph run buffer. - /// - /// The glyph typeface. - /// The font rendering em size. - /// The length. - /// An . - /// - /// This buffer holds glyph indices, glyph advances and glyph positions. - /// - IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + /// The glyph indices. + /// The glyph advances. + /// The glyph offsets. + /// + IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList? glyphAdvances, IReadOnlyList? glyphOffsets); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index a9bb16c7df..adf0569551 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -80,6 +80,12 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty LineHeightProperty = TextBlock.LineHeightProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty LetterSpacingProperty = + TextBlock.LetterSpacingProperty.AddOwner(); + /// /// Defines the property. /// @@ -212,6 +218,15 @@ namespace Avalonia.Controls.Presenters set => SetValue(LineHeightProperty, value); } + /// + /// Gets or sets the letter spacing. + /// + public double LetterSpacing + { + get => GetValue(LetterSpacingProperty); + set => SetValue(LetterSpacingProperty, value); + } + /// /// Gets or sets the text alignment. /// @@ -333,7 +348,7 @@ namespace Avalonia.Controls.Presenters var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, - flowDirection: FlowDirection, lineHeight: LineHeight); + flowDirection: FlowDirection, lineHeight: LineHeight, letterSpacing: LetterSpacing); return textLayout; } @@ -916,6 +931,9 @@ namespace Avalonia.Controls.Presenters case nameof(TextAlignment): case nameof(TextWrapping): + case nameof(LineHeight): + case nameof(LetterSpacing): + case nameof(SelectionStart): case nameof(SelectionEnd): case nameof(SelectionForegroundBrush): diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index f79d3f8296..0492c2c1e3 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -82,6 +82,15 @@ namespace Avalonia.Controls validate: IsValidLineHeight, inherits: true); + /// + /// Defines the property. + /// + public static readonly AttachedProperty LetterSpacingProperty = + AvaloniaProperty.RegisterAttached( + nameof(LetterSpacing), + 0, + inherits: true); + /// /// Defines the property. /// @@ -262,6 +271,15 @@ namespace Avalonia.Controls set => SetValue(LineHeightProperty, value); } + /// + /// Gets or sets the letter spacing. + /// + public double LetterSpacing + { + get => GetValue(LetterSpacingProperty); + set => SetValue(LetterSpacingProperty, value); + } + /// /// Gets or sets the maximum number of text lines. /// @@ -475,6 +493,35 @@ namespace Avalonia.Controls control.SetValue(LineHeightProperty, height); } + /// + /// Reads the attached property from the given element + /// + /// The element to which to read the attached property. + public static double GetLetterSpacing(Control control) + { + if (control == null) + { + throw new ArgumentNullException(nameof(control)); + } + + return control.GetValue(LetterSpacingProperty); + } + + /// + /// Writes the attached property LetterSpacing to the given element. + /// + /// The element to which to write the attached property. + /// The property value to set + public static void SetLetterSpacing(Control control, double letterSpacing) + { + if (control == null) + { + throw new ArgumentNullException(nameof(control)); + } + + control.SetValue(LetterSpacingProperty, letterSpacing); + } + /// /// Reads the attached property from the given element /// @@ -584,7 +631,7 @@ namespace Avalonia.Controls Foreground); var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, - defaultProperties, TextWrapping, LineHeight, 0); + defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing); ITextSource textSource; @@ -744,9 +791,10 @@ namespace Avalonia.Controls case nameof(FlowDirection): - case nameof(Padding): - case nameof(LineHeight): - case nameof(MaxLines): + case nameof (Padding): + case nameof (LineHeight): + case nameof (LetterSpacing): + case nameof (MaxLines): case nameof(Text): case nameof(TextDecorations): diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index da4e90fb66..85c1c9a9d1 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -114,6 +114,12 @@ namespace Avalonia.Controls public static readonly StyledProperty LineHeightProperty = TextBlock.LineHeightProperty.AddOwner(); + /// + /// Defines see property. + /// + public static readonly StyledProperty LetterSpacingProperty = + TextBlock.LetterSpacingProperty.AddOwner(); + public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); @@ -378,6 +384,12 @@ namespace Avalonia.Controls set => SetValue(MaxLinesProperty, value); } + public double LetterSpacing + { + get => GetValue(LetterSpacingProperty); + set => SetValue(LetterSpacingProperty, value); + } + /// /// Gets or sets the line height. /// diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index fcd7f1e31f..501d239cee 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -115,19 +115,16 @@ namespace Avalonia.Headless return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { - return new HeadlessGlyphRunBufferStub(); + return new HeadlessGlyphRunStub(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new HeadlessHorizontalGlyphRunBufferStub(); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + class HeadlessGlyphRunStub : IGlyphRunImpl { - return new HeadlessPositionedGlyphRunBufferStub(); + public void Dispose() + { + } } class HeadlessGeometryStub : IGeometryImpl @@ -213,33 +210,6 @@ namespace Avalonia.Headless public Matrix Transform { get; } } - class HeadlessGlyphRunBufferStub : IGlyphRunBuffer - { - public Span GlyphIndices => Span.Empty; - - public IGlyphRunImpl Build() - { - return new HeadlessGlyphRunStub(); - } - } - - class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer - { - public Span GlyphPositions => Span.Empty; - } - - class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer - { - public Span GlyphPositions => Span.Empty; - } - - class HeadlessGlyphRunStub : IGlyphRunImpl - { - public void Dispose() - { - } - } - class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl { public HeadlessStreamingGeometryStub() : base(Rect.Empty) diff --git a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml index 17c69da8fd..db487ef76b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml @@ -161,6 +161,7 @@ TextAlignment="{TemplateBinding TextAlignment}" TextWrapping="{TemplateBinding TextWrapping}" LineHeight="{TemplateBinding LineHeight}" + LetterSpacing="{TemplateBinding LetterSpacing}" PasswordChar="{TemplateBinding PasswordChar}" RevealPassword="{TemplateBinding RevealPassword}" SelectionBrush="{TemplateBinding SelectionBrush}" diff --git a/src/Avalonia.Themes.Simple/Controls/TextBox.xaml b/src/Avalonia.Themes.Simple/Controls/TextBox.xaml index 5fa6412688..0bcb425ca9 100644 --- a/src/Avalonia.Themes.Simple/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Simple/Controls/TextBox.xaml @@ -149,14 +149,14 @@ CaretBrush="{TemplateBinding CaretBrush}" CaretIndex="{TemplateBinding CaretIndex}" LineHeight="{TemplateBinding LineHeight}" + LetterSpacing="{TemplateBinding LetterSpacing}" PasswordChar="{TemplateBinding PasswordChar}" RevealPassword="{TemplateBinding RevealPassword}" SelectionBrush="{TemplateBinding SelectionBrush}" SelectionEnd="{TemplateBinding SelectionEnd}" SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" SelectionStart="{TemplateBinding SelectionStart}" - Text="{TemplateBinding Text, - Mode=TwoWay}" + Text="{TemplateBinding Text,Mode=TwoWay}" TextAlignment="{TemplateBinding TextAlignment}" TextWrapping="{TemplateBinding TextWrapping}" /> diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index d11f4aa7d3..71bdc1bd6b 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -69,10 +69,6 @@ namespace Avalonia.Skia public int GlyphCount { get; } - public bool IsFakeBold { get; } - - public bool IsFakeItalic { get; } - public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) { metrics = default; diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index a9696efbd4..dd3badb2d8 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -12,8 +12,6 @@ using Avalonia.OpenGL.Imaging; using Avalonia.Platform; using Avalonia.Media.Imaging; using SkiaSharp; -using System.Runtime.InteropServices; -using System.Drawing; namespace Avalonia.Skia { @@ -79,7 +77,7 @@ namespace Avalonia.Skia var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize) { Size = fontRenderingEmSize, - Edging = SKFontEdging.Antialias, + Edging = SKFontEdging.Alias, Hinting = SKFontHinting.None, LinearMetrics = true }; @@ -244,85 +242,91 @@ namespace Avalonia.Skia "Current GPU acceleration backend does not support OpenGL integration"); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, + IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + { + if (glyphTypeface == null) + { + throw new ArgumentNullException(nameof(glyphTypeface)); + } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + if (glyphIndices == null) + { + throw new ArgumentNullException(nameof(glyphIndices)); + } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl; - private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer - { - protected readonly SKTextBlobBuilder _builder; - protected readonly SKFont _font; + var font = new SKFont + { + LinearMetrics = true, + Subpixel = true, + Edging = SKFontEdging.SubpixelAntialias, + Hinting = SKFontHinting.Full, + Size = (float)fontRenderingEmSize, + Typeface = glyphTypefaceImpl.Typeface, + Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0, + SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0 + }; + + var builder = new SKTextBlobBuilder(); - public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + var count = glyphIndices.Count; + + if(glyphOffsets != null && glyphAdvances != null) { - _builder = new SKTextBlobBuilder(); + var runBuffer = builder.AllocatePositionedRun(font, count); - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; + var glyphSpan = runBuffer.GetGlyphSpan(); + var positionSpan = runBuffer.GetPositionSpan(); - _font = new SKFont - { - Subpixel = true, - Edging = SKFontEdging.SubpixelAntialias, - Hinting = SKFontHinting.Full, - LinearMetrics = true, - Size = fontRenderingEmSize, - Typeface = glyphTypefaceImpl.Typeface, - Embolden = glyphTypefaceImpl.IsFakeBold, - SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0 - }; - } + var currentX = 0.0; - public abstract Span GlyphIndices { get; } + for (int i = 0; i < glyphOffsets.Count; i++) + { + var offset = glyphOffsets[i]; - public IGlyphRunImpl Build() - { - return new GlyphRunImpl(_builder.Build()); - } - } + glyphSpan[i] = glyphIndices[i]; - private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase - { - private readonly SKRunBuffer _buffer; + positionSpan[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y); - public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) - { - _buffer = _builder.AllocateRun(_font, length, 0, 0); + currentX += glyphAdvances[i]; + } } + else + { + if(glyphAdvances != null) + { + var runBuffer = builder.AllocateHorizontalRun(font, count, 0); - public override Span GlyphIndices => _buffer.GetGlyphSpan(); - } + var glyphSpan = runBuffer.GetGlyphSpan(); + var positionSpan = runBuffer.GetPositionSpan(); - private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer - { - private readonly SKHorizontalRunBuffer _buffer; + var currentX = 0.0; - public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) - { - _buffer = _builder.AllocateHorizontalRun(_font, length, 0); - } + for (int i = 0; i < glyphOffsets.Count; i++) + { + glyphSpan[i] = glyphIndices[i]; - public override Span GlyphIndices => _buffer.GetGlyphSpan(); + positionSpan[i] = (float)currentX; - public Span GlyphPositions => _buffer.GetPositionSpan(); - } + currentX += glyphAdvances[i]; + } + } + else + { + var runBuffer = builder.AllocateRun(font, count, 0, 0); - private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer - { - private readonly SKPositionedRunBuffer _buffer; + var glyphSpan = runBuffer.GetGlyphSpan(); - public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) - { - _buffer = _builder.AllocatePositionedRun(_font, length); + for (int i = 0; i < glyphOffsets.Count; i++) + { + glyphSpan[i] = glyphIndices[i]; + } + } } - public override Span GlyphIndices => _buffer.GetGlyphSpan(); - - public Span GlyphPositions => MemoryMarshal.Cast(_buffer.GetPositionSpan()); + return new GlyphRunImpl(builder.Build()); } } } diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index d6bb37a06a..eaf588c27d 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -60,7 +60,7 @@ namespace Avalonia.Skia var glyphCluster = (int)(sourceInfo.Cluster); - var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale); + var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing; var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 8103f89dad..4d307c9762 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -10,10 +10,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; -using TextAlignment = Avalonia.Media.TextAlignment; using SharpDX.Mathematics.Interop; -using System.Runtime.InteropServices; -using System.Drawing; namespace Avalonia { @@ -160,6 +157,72 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children); public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2); + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, + IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + { + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; + + var glyphCount = glyphIndices.Count; + + var run = new SharpDX.DirectWrite.GlyphRun + { + FontFace = glyphTypefaceImpl.FontFace, + FontSize = (float)fontRenderingEmSize + }; + + var indices = new short[glyphCount]; + + for (var i = 0; i < glyphCount; i++) + { + indices[i] = (short)glyphIndices[i]; + } + + run.Indices = indices; + + run.Advances = new float[glyphCount]; + + var scale = (float)(fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight); + + if (glyphAdvances == null) + { + for (var i = 0; i < glyphCount; i++) + { + var advance = glyphTypeface.GetGlyphAdvance(glyphIndices[i]) * scale; + + run.Advances[i] = advance; + } + } + else + { + for (var i = 0; i < glyphCount; i++) + { + var advance = (float)glyphAdvances[i]; + + run.Advances[i] = advance; + } + } + + if (glyphOffsets == null) + { + return new GlyphRunImpl(run); + } + + run.Offsets = new GlyphOffset[glyphCount]; + + for (var i = 0; i < glyphCount; i++) + { + var (x, y) = glyphOffsets[i]; + + run.Offsets[i] = new GlyphOffset + { + AdvanceOffset = (float)x, + AscenderOffset = (float)y + }; + } + + return new GlyphRunImpl(run); + } + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) @@ -260,68 +323,6 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride); } - private class DWGlyphRunBuffer : IGlyphRunBuffer - { - protected readonly SharpDX.DirectWrite.GlyphRun _dwRun; - - public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; - - _dwRun = new SharpDX.DirectWrite.GlyphRun - { - FontFace = glyphTypefaceImpl.FontFace, - FontSize = fontRenderingEmSize, - Indices = new short[length] - }; - } - - public Span GlyphIndices => MemoryMarshal.Cast(_dwRun.Indices.AsSpan()); - - public IGlyphRunImpl Build() - { - return new GlyphRunImpl(_dwRun); - } - } - - private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer - { - public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - : base(glyphTypeface, fontRenderingEmSize, length) - { - _dwRun.Advances = new float[length]; - } - - public Span GlyphPositions => _dwRun.Advances.AsSpan(); - } - - private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer - { - public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - : base(glyphTypeface, fontRenderingEmSize, length) - { - _dwRun.Advances = new float[length]; - _dwRun.Offsets = new GlyphOffset[length]; - } - - public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan()); - } - - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - } - - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - } - public bool SupportsIndividualRoundRects => false; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 5f8eb45f71..10db08f302 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -72,7 +72,7 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { throw new NotImplementedException(); } @@ -126,21 +126,6 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - class MockStreamGeometry : IStreamGeometryImpl { private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 34f0dfef11..4170de71e6 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -5,6 +5,7 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; using Avalonia.Media.Imaging; +using Microsoft.Diagnostics.Runtime; namespace Avalonia.Benchmarks { @@ -117,19 +118,9 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { - throw new NotImplementedException(); - } - - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); + return new MockGlyphRun(); } public bool SupportsIndividualRoundRects => true; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index a315158e1b..316926b00c 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -425,7 +425,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var defaultProperties = new GenericTextRunProperties(Typeface.Default); var paragraphProperties = new GenericTextParagraphProperties(flowDirection, textAlignment, true, true, - defaultProperties, TextWrapping.NoWrap, 0, 0); + defaultProperties, TextWrapping.NoWrap, 0, 0, 0); var textSource = new SingleBufferTextSource(text, defaultProperties); var formatter = new TextFormatterImpl(); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 33c18c5064..87de9ed11f 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -878,7 +878,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = formatter.FormatLine(textSource, 0, 200, - new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0)); + new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, + true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0)); var textBounds = textLine.GetTextBounds(0, 3); @@ -924,7 +925,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = formatter.FormatLine(textSource, 0, 200, - new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0)); + new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, + true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0)); var textBounds = textLine.GetTextBounds(0, 4); diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs new file mode 100644 index 0000000000..24948aff01 --- /dev/null +++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs @@ -0,0 +1,12 @@ +using Avalonia.Platform; + +namespace Avalonia.UnitTests +{ + public class MockGlyphRun : IGlyphRunImpl + { + public void Dispose() + { + + } + } +} diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 586436ef7f..0f951ed867 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -142,7 +142,7 @@ namespace Avalonia.UnitTests throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { return Mock.Of(); } From 79e2d44005f27b3d1611ee90a8c89c3196122770 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 2 Nov 2022 16:09:01 +0600 Subject: [PATCH 102/122] Only use scoped keyword when building with .NET 7 SDK --- Directory.Build.targets | 5 +++++ src/Avalonia.Base/Media/PathMarkupParser.cs | 12 ++++++++++-- src/Avalonia.Base/Utilities/IdentifierParser.cs | 6 +++++- .../Markup/Parsers/BindingExpressionGrammar.cs | 12 ++++++++++-- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 Directory.Build.targets diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000000..73954c7f4d --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,5 @@ + + + $(DefineConstants);NET7SDK + + diff --git a/src/Avalonia.Base/Media/PathMarkupParser.cs b/src/Avalonia.Base/Media/PathMarkupParser.cs index cf12bf5126..5e808488fc 100644 --- a/src/Avalonia.Base/Media/PathMarkupParser.cs +++ b/src/Avalonia.Base/Media/PathMarkupParser.cs @@ -188,7 +188,11 @@ namespace Avalonia.Media _isOpen = true; } - private void SetFillRule(scoped ref ReadOnlySpan span) + private void SetFillRule( +#if NET7SDK + scoped +#endif + ref ReadOnlySpan span) { ThrowIfDisposed(); @@ -452,7 +456,11 @@ namespace Avalonia.Media return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0])); } - private static bool ReadArgument(scoped ref ReadOnlySpan remaining, out ReadOnlySpan argument) + private static bool ReadArgument( +#if NET7SDK + scoped +#endif + ref ReadOnlySpan remaining, out ReadOnlySpan argument) { remaining = SkipWhitespace(remaining); if (remaining.IsEmpty) diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs index ee176a6b85..76e6459e2e 100644 --- a/src/Avalonia.Base/Utilities/IdentifierParser.cs +++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs @@ -8,7 +8,11 @@ namespace Avalonia.Utilities #endif static class IdentifierParser { - public static ReadOnlySpan ParseIdentifier(this scoped ref CharacterReader r) + public static ReadOnlySpan ParseIdentifier(this +#if NET7SDK + scoped +#endif + ref CharacterReader r) { if (IsValidIdentifierStart(r.Peek)) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 0a9fbcfacb..21c0d97c74 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -168,7 +168,11 @@ namespace Avalonia.Markup.Parsers } } - private static State ParseAttachedProperty(scoped ref CharacterReader r, List nodes) + private static State ParseAttachedProperty( +#if NET7SDK + scoped +#endif + ref CharacterReader r, List nodes) { var (ns, owner) = ParseTypeName(ref r); @@ -318,7 +322,11 @@ namespace Avalonia.Markup.Parsers return State.AfterMember; } - private static TypeName ParseTypeName(scoped ref CharacterReader r) + private static TypeName ParseTypeName( +#if NET7SDK + scoped +#endif + ref CharacterReader r) { ReadOnlySpan ns, typeName; ns = ReadOnlySpan.Empty; From 1b7544509d39fba46af148e1d1f4412884dc7491 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 2 Nov 2022 11:13:51 +0000 Subject: [PATCH 103/122] fix minimise button being disabled when either a parent or a dialog. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ddc50c26b6..2443965957 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { START_COM_CALL; @autoreleasepool { - _isDialog = isDialog; + _isDialog = isDialog || _parent != nullptr; WindowBaseImpl::Show(activate, isDialog); @@ -96,6 +96,8 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) { auto cparent = dynamic_cast(parent); _parent = cparent; + + _isDialog = _parent != nullptr; if(_parent != nullptr && Window != nullptr){ // If one tries to show a child window with a minimized parent window, then the parent window will be From 9aca3d827c24003106a60da5acbded5810e982c9 Mon Sep 17 00:00:00 2001 From: hacklex Date: Wed, 2 Nov 2022 18:00:05 +0300 Subject: [PATCH 104/122] Replaced hard-coded indexer with its actual name This was possibly just overlooked since nobody probably ever changed the name from "Item". --- src/Avalonia.Base/Collections/AvaloniaDictionary.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 750fb263f5..35a391f2cb 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -81,7 +81,7 @@ namespace Avalonia.Collections if (replace) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]")); if (CollectionChanged != null) { @@ -148,7 +148,7 @@ namespace Avalonia.Collections { _inner.Remove(key); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]")); if (CollectionChanged != null) { @@ -208,7 +208,7 @@ namespace Avalonia.Collections private void NotifyAdd(TKey key, TValue value) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]")); if (CollectionChanged != null) From a8150e24ba76f0600bc11a6d74e3f50ea09eb7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20=C8=9Aurcan?= <6237485+equalent@users.noreply.github.com> Date: Thu, 3 Nov 2022 01:55:21 +0400 Subject: [PATCH 105/122] Fix support URL in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1009e86c29..c2be487af3 100644 --- a/readme.md +++ b/readme.md @@ -104,7 +104,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l ## Commercial Support -We have a range of [support plans available](https://avaloniaui.net/support.html) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process. +We have a range of [support plans available](https://avaloniaui.net/support) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process. *Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)* ## .NET Foundation From 4670536b82044e9cc21d2d3f03c98e1781453efb Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 3 Nov 2022 01:52:47 -0400 Subject: [PATCH 106/122] [ARM only] Fix incorrect parameters passed to the CreateDrawingSurface method (#9342) * Use CreateDrawingSurface2 method with a correct SIZE struct * Fix old method definition just for case * Fix ICompositionDrawingSurface.GetSize method too --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 6 ++++++ .../WinRT/Composition/WinUICompositorConnection.cs | 3 ++- src/Windows/Avalonia.Win32/WinRT/winrt.idl | 11 +++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f1ed53ea99..4afb1068df 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -2104,6 +2104,12 @@ namespace Avalonia.Win32.Interop public int Y; } + public struct SIZE_F + { + public float X; + public float Y; + } + public struct RECT { public int left; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 8a41c00add..6da17b8ea5 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -174,8 +174,9 @@ namespace Avalonia.Win32.WinRT.Composition using var sc = _syncContext.EnsureLocked(); using var desktopTarget = _compositorDesktopInterop.CreateDesktopWindowTarget(hWnd, 0); using var target = desktopTarget.QueryInterface(); + using var device2 = _device.QueryInterface(); - using var drawingSurface = _device.CreateDrawingSurface(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized, + using var drawingSurface = device2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied); using var surface = drawingSurface.QueryInterface(); using var surfaceInterop = drawingSurface.QueryInterface(); diff --git a/src/Windows/Avalonia.Win32/WinRT/winrt.idl b/src/Windows/Avalonia.Win32/WinRT/winrt.idl index 851df9dae6..ffb98fafc8 100644 --- a/src/Windows/Avalonia.Win32/WinRT/winrt.idl +++ b/src/Windows/Avalonia.Win32/WinRT/winrt.idl @@ -8,6 +8,7 @@ @clr-map Matrix4x4 System.Numerics.Matrix4x4 @clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT @clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE +@clr-map SIZE_F Avalonia.Win32.Interop.UnmanagedMethods.SIZE_F @clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT @clr-map HWND IntPtr @clr-map BOOL int @@ -442,12 +443,18 @@ interface IDesktopWindowContentBridgeInterop : IUnknown [uuid(FB22C6E1-80A2-4667-9936-DBEAF6EEFE95)] interface ICompositionGraphicsDevice : IInspectable { - HRESULT CreateDrawingSurface([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat, + HRESULT CreateDrawingSurface([in] SIZE_F sizePixels, [in] DirectXPixelFormat pixelFormat, [in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result); HRESULT AddRenderingDeviceReplaced(void* handler, void* token); HRESULT RemoveRenderingDeviceReplaced([in] int token); } +[uuid(0FB8BDF6-C0F0-4BCC-9FB8-084982490D7D)] +interface ICompositionGraphicsDevice2 : IInspectable +{ + HRESULT CreateDrawingSurface2([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat, [in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result); +} + [uuid(1527540D-42C7-47A6-A408-668F79A90DFB)] interface ICompositionSurface : IInspectable { @@ -465,7 +472,7 @@ interface ICompositionDrawingSurface : IInspectable { [propget] HRESULT GetAlphaMode([out] [retval] DirectXAlphaMode* value); [propget] HRESULT GetPixelFormat([out] [retval] DirectXPixelFormat* value); - [propget] HRESULT GetSize([out] [retval] POINT* value); + [propget] HRESULT GetSize([out] [retval] SIZE_F* value); } enum CompositionBitmapInterpolationMode From 2f5c5494b41e858c814182af311db891d75eac0c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 3 Nov 2022 09:37:07 +0100 Subject: [PATCH 107/122] Add failing test --- .../TextBlockTests.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index be8062322e..84ae0b6453 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Documents; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.Rendering; using Avalonia.UnitTests; using Moq; @@ -165,9 +166,9 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope) ); - target.Inlines!.Add("123456"); + target.Inlines!.AddText("123456"); target.Inlines.Add(new InlineUIContainer(button)); - target.Inlines.Add("123456"); + target.Inlines.AddText("123456"); target.Measure(Size.Infinity); @@ -181,5 +182,26 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(60, button.Bounds.Left); } } + + [Fact] + public void Setting_Text_Should_Reset_Inlines() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new TextBlock(); + + target.Inlines.Add(new Run("Hello World")); + + Assert.Equal("Hello World", target.Text); + + Assert.Equal(1, target.Inlines.Count); + + target.Text = "1234"; + + Assert.Equal("1234", target.Text); + + Assert.Equal(0, target.Inlines.Count); + } + } } } From b370a0b6494a0b8d4d15152590a9e9e73482ab16 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 3 Nov 2022 09:37:42 +0100 Subject: [PATCH 108/122] Fix TextBlock TextProperty update handling for non complex content --- .../Documents/InlineCollection.cs | 52 ++++++++----------- src/Avalonia.Controls/TextBlock.cs | 19 ++----- 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index a265f88e21..0329429051 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -89,55 +89,49 @@ namespace Avalonia.Controls.Documents } + public override void Add(Inline inline) + { + if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text)) + { + base.Add(new Run(textBlock._text)); + + textBlock._text = null; + } + + base.Add(inline); + } + /// - /// Add a text segment to the collection. + /// Adds a text segment to the collection. /// /// For non complex content this appends the text to the end of currently held text. /// For complex content this adds a to the collection. /// /// - /// + /// The to be added text. public void Add(string text) { AddText(text); } - public override void Add(Inline inline) - { - OnAdd(); - - base.Add(inline); - } - - public void Add(IControl child) + /// + /// Adds a control wrapped inside a to the collection. + /// + /// The to be added control. + public void Add(IControl control) { - OnAdd(); - - base.Add(new InlineUIContainer(child)); + Add(new InlineUIContainer(control)); } - private void AddText(string text) + internal void AddText(string text) { - if (LogicalChildren is TextBlock textBlock && !textBlock.HasComplexContent) + if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent) { textBlock._text += text; } else { - base.Add(new Run(text)); - } - } - - private void OnAdd() - { - if (LogicalChildren is TextBlock textBlock) - { - if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text)) - { - base.Add(new Run(textBlock._text)); - - textBlock._text = null; - } + Add(new Run(text)); } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 0492c2c1e3..c8e05e5cb3 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -597,23 +597,12 @@ namespace Avalonia.Controls protected virtual void SetText(string? text) { - if (Inlines != null && Inlines.Count > 0) - { - var oldValue = Inlines.Text; - - if (!string.IsNullOrEmpty(text)) - { - Inlines.Add(text); - } - - text = Inlines.Text; - - RaisePropertyChanged(TextProperty, oldValue, text); - } - else + if (HasComplexContent) { - SetAndRaise(TextProperty, ref _text, text); + Inlines?.Clear(); } + + SetAndRaise(TextProperty, ref _text, text); } /// From 0d889da820ed1acf49b265a223cdfdb9245fe923 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 3 Nov 2022 09:42:10 +0100 Subject: [PATCH 109/122] Remove redundant member --- .../Documents/InlineCollection.cs | 21 +++++++------------ .../TextBlockTests.cs | 4 ++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 0329429051..5b68402f87 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -111,7 +111,14 @@ namespace Avalonia.Controls.Documents /// The to be added text. public void Add(string text) { - AddText(text); + if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent) + { + textBlock._text += text; + } + else + { + Add(new Run(text)); + } } /// @@ -123,18 +130,6 @@ namespace Avalonia.Controls.Documents Add(new InlineUIContainer(control)); } - internal void AddText(string text) - { - if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent) - { - textBlock._text += text; - } - else - { - Add(new Run(text)); - } - } - /// /// Raised when an inline in the collection changes. /// diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index 84ae0b6453..de5e5a8ea3 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -166,9 +166,9 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope) ); - target.Inlines!.AddText("123456"); + target.Inlines!.Add("123456"); target.Inlines.Add(new InlineUIContainer(button)); - target.Inlines.AddText("123456"); + target.Inlines.Add("123456"); target.Measure(Size.Infinity); From a539f34f943a10604547881993384cf4f084dea8 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 3 Nov 2022 22:44:53 -0400 Subject: [PATCH 110/122] Implement windows IntermediatePoints for the Mouse input --- .../Interop/UnmanagedMethods.cs | 14 ++++ .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 84 ++++++++++++++++++- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 + 3 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f1ed53ea99..67d1deb9b7 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1083,6 +1083,20 @@ namespace Avalonia.Win32.Interop public const int SizeOf_BITMAPINFOHEADER = 40; + [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] + unsafe internal static extern int GetMouseMovePointsEx( + uint cbSize, MOUSEMOVEPOINT* pointsIn, + MOUSEMOVEPOINT* pointsBufferOut, int nBufPoints, uint resolution); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] // For GetMouseMovePointsEx + public struct MOUSEMOVEPOINT + { + public int x; //Specifies the x-coordinate of the mouse + public int y; //Specifies the x-coordinate of the mouse + public int time; //Specifies the time stamp of the mouse coordinate + public IntPtr dwExtraInfo; //Specifies extra information associated with this coordinate. + } + [DllImport("user32.dll", SetLastError = true)] public static extern bool IsMouseInPointerEnabled(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index f8785371d9..c4136a239e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -272,13 +272,34 @@ namespace Avalonia.Win32 TrackMouseEvent(ref tm); } + var point = DipFromLParam(lParam); + + // Prepare points for the IntermediatePoints call. + var p = new POINT() + { + X = (int)(point.X * RenderScaling), + Y = (int)(point.Y * RenderScaling) + }; + ClientToScreen(_hwnd, ref p); + var currPoint = new MOUSEMOVEPOINT() + { + x = p.X & 0xFFFF, + y = p.Y & 0xFFFF, + time = (int)timestamp + }; + var prevPoint = _lastWmMousePoint; + _lastWmMousePoint = currPoint; + e = new RawPointerEventArgs( _mouseDevice, timestamp, _owner, RawPointerEventType.Move, - DipFromLParam(lParam), - GetMouseModifiers(wParam)); + point, + GetMouseModifiers(wParam)) + { + IntermediatePoints = new Lazy>(() => CreateLazyIntermediatePoints(currPoint, prevPoint)) + }; break; } @@ -782,6 +803,65 @@ namespace Avalonia.Win32 return null; } + private unsafe IReadOnlyList CreateLazyIntermediatePoints(MOUSEMOVEPOINT movePoint, MOUSEMOVEPOINT prevMovePoint) + { + // To understand some of this code, please check MS docs: + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmousemovepointsex#remarks + + fixed (MOUSEMOVEPOINT* movePoints = s_mouseHistoryInfos) + { + var movePointCopy = movePoint; + movePointCopy.time = 0; // empty "time" as otherwise WinAPI will always fail + int pointsCount = GetMouseMovePointsEx( + (uint)(Marshal.SizeOf(movePointCopy)), + &movePointCopy, movePoints, s_mouseHistoryInfos.Length, + 1); + + // GetMouseMovePointsEx can return -1 if point wasn't found or there is so beeg delay that original points were erased from the buffer. + if (pointsCount <= 1) + { + return Array.Empty(); + } + + s_intermediatePointsPooledList.Clear(); + s_intermediatePointsPooledList.Capacity = pointsCount; + for (int i = pointsCount - 1; i >= 1; i--) + { + var historyInfo = s_mouseHistoryInfos[i]; + // Skip points newer than current point. + if (historyInfo.time > movePoint.time || + (historyInfo.time == movePoint.time && + historyInfo.x == movePoint.x && + historyInfo.y == movePoint.y)) + { + continue; + } + // Skip poins older from previous WM_MOUSEMOVE point. + if (historyInfo.time < prevMovePoint.time || + (historyInfo.time == prevMovePoint.time && + historyInfo.x == prevMovePoint.x && + historyInfo.y == prevMovePoint.y)) + { + continue; + } + + // To support multiple screens. + if (historyInfo.x > 32767) + historyInfo.x -= 65536; + + if (historyInfo.y > 32767) + historyInfo.y -= 65536; + + var point = PointToClient(new PixelPoint(historyInfo.x, historyInfo.y)); + s_intermediatePointsPooledList.Add(new RawPointerPoint + { + Position = point + }); + } + return s_intermediatePointsPooledList; + } + } + private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId) { return device is TouchDevice diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5374614379..9ed1a50ff2 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -65,6 +65,7 @@ namespace Avalonia.Win32 private bool _isUsingComposition; private IBlurHost _blurHost; private PlatformResizeReason _resizeReason; + private MOUSEMOVEPOINT _lastWmMousePoint; #if USE_MANAGED_DRAG private readonly ManagedWindowResizeDragHelper _managedDrag; @@ -107,6 +108,7 @@ namespace Avalonia.Win32 private static readonly POINTER_TOUCH_INFO[] s_historyTouchInfos = new POINTER_TOUCH_INFO[MaxPointerHistorySize]; private static readonly POINTER_PEN_INFO[] s_historyPenInfos = new POINTER_PEN_INFO[MaxPointerHistorySize]; private static readonly POINTER_INFO[] s_historyInfos = new POINTER_INFO[MaxPointerHistorySize]; + private static readonly MOUSEMOVEPOINT[] s_mouseHistoryInfos = new MOUSEMOVEPOINT[64]; public WindowImpl() { From 75d798d00a5ff2f75f7382f4be374a70ab957f35 Mon Sep 17 00:00:00 2001 From: zhouzj Date: Fri, 4 Nov 2022 17:04:34 +0800 Subject: [PATCH 111/122] Fix the size issue of indicator border in ProgressBar --- src/Avalonia.Controls/ProgressBar.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 1e406157d7..71a7a58da4 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls public static readonly StyledProperty ProgressTextFormatProperty = AvaloniaProperty.Register(nameof(ProgressTextFormat), "{1:0}%"); - + public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); @@ -136,7 +136,7 @@ namespace Avalonia.Controls get { return _percentage; } private set { SetAndRaise(PercentageProperty, ref _percentage, value); } } - + public double IndeterminateStartingOffset { get => _indeterminateStartingOffset; @@ -156,6 +156,7 @@ namespace Avalonia.Controls MinimumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); MaximumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); IsIndeterminateProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); + OrientationProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); } public ProgressBar() @@ -216,13 +217,13 @@ namespace Avalonia.Controls { // dispose any previous track size listener _trackSizeChangedListener?.Dispose(); - + _indicator = e.NameScope.Get("PART_Indicator"); // listen to size changes of the indicators track (parent) and update the indicator there. _trackSizeChangedListener = _indicator.Parent?.GetPropertyChangedObservable(BoundsProperty) .Subscribe(_ => UpdateIndicator()); - + UpdateIndicator(); } @@ -230,7 +231,7 @@ namespace Avalonia.Controls { // Gets the size of the parent indicator container var barSize = _indicator?.Parent?.Bounds.Size ?? Bounds.Size; - + if (_indicator != null) { if (IsIndeterminate) @@ -268,10 +269,18 @@ namespace Avalonia.Controls { double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum); + // When the Orientation changed, the indicator's Width or Height should set to double.NaN. if (Orientation == Orientation.Horizontal) + { _indicator.Width = barSize.Width * percent; + _indicator.Height = double.NaN; + } else + { + _indicator.Width = double.NaN; _indicator.Height = barSize.Height * percent; + } + Percentage = percent * 100; } From 10351871303d92e01cd6149d8999798062679bda Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 Nov 2022 12:08:41 +0100 Subject: [PATCH 112/122] Added desktop solution filter. Loads on the projects required to run on desktop platforms. --- Avalonia.Desktop.slnf | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Avalonia.Desktop.slnf diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf new file mode 100644 index 0000000000..6ba05332be --- /dev/null +++ b/Avalonia.Desktop.slnf @@ -0,0 +1,60 @@ +{ + "solution": { + "path": "Avalonia.sln", + "projects": [ + "packages\\Avalonia\\Avalonia.csproj", + "samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj", + "samples\\ControlCatalog\\ControlCatalog.csproj", + "samples\\IntegrationTestApp\\IntegrationTestApp.csproj", + "samples\\MiniMvvm\\MiniMvvm.csproj", + "samples\\SampleControls\\ControlSamples.csproj", + "samples\\Sandbox\\Sandbox.csproj", + "src\\Avalonia.Base\\Avalonia.Base.csproj", + "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj", + "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj", + "src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj", + "src\\Avalonia.Controls\\Avalonia.Controls.csproj", + "src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj", + "src\\Avalonia.Desktop\\Avalonia.Desktop.csproj", + "src\\Avalonia.Diagnostics\\Avalonia.Diagnostics.csproj", + "src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj", + "src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj", + "src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj", + "src\\Avalonia.Headless\\Avalonia.Headless.csproj", + "src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj", + "src\\Avalonia.Native\\Avalonia.Native.csproj", + "src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj", + "src\\Avalonia.ReactiveUI\\Avalonia.ReactiveUI.csproj", + "src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj", + "src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj", + "src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj", + "src\\Avalonia.X11\\Avalonia.X11.csproj", + "src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj", + "src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj", + "src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj", + "src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj", + "src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj", + "src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj", + "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj", + "src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj", + "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", + "src\\tools\\DevGenerators\\DevGenerators.csproj", + "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj", + "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj", + "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj", + "tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj", + "tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj", + "tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj", + "tests\\Avalonia.Direct2D1.RenderTests\\Avalonia.Direct2D1.RenderTests.csproj", + "tests\\Avalonia.Direct2D1.UnitTests\\Avalonia.Direct2D1.UnitTests.csproj", + "tests\\Avalonia.IntegrationTests.Appium\\Avalonia.IntegrationTests.Appium.csproj", + "tests\\Avalonia.LeakTests\\Avalonia.LeakTests.csproj", + "tests\\Avalonia.Markup.UnitTests\\Avalonia.Markup.UnitTests.csproj", + "tests\\Avalonia.Markup.Xaml.UnitTests\\Avalonia.Markup.Xaml.UnitTests.csproj", + "tests\\Avalonia.ReactiveUI.UnitTests\\Avalonia.ReactiveUI.UnitTests.csproj", + "tests\\Avalonia.Skia.RenderTests\\Avalonia.Skia.RenderTests.csproj", + "tests\\Avalonia.Skia.UnitTests\\Avalonia.Skia.UnitTests.csproj", + "tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj" + ] + } +} \ No newline at end of file From 848ca76216ea23ea43a9da0f24faa6b660553b2d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 4 Nov 2022 12:12:34 +0100 Subject: [PATCH 113/122] Update build instructions. --- Documentation/build.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/Documentation/build.md b/Documentation/build.md index ddd38be887..fd6b26337c 100644 --- a/Documentation/build.md +++ b/Documentation/build.md @@ -1,8 +1,8 @@ # Windows -Avalonia requires at least Visual Studio 2022 and dotnet 6 SDK 6.0.100 to build on all platforms. +Avalonia requires at least Visual Studio 2022 and dotnet 7-rc2 SDK 7.0.100-rc.2 to build on all platforms. -### Clone the Avalonia repository +## Clone the Avalonia repository ``` git clone https://github.com/AvaloniaUI/Avalonia.git @@ -10,15 +10,30 @@ cd Avalonia git submodule update --init ``` -### Install the required version of the .NET Core SDK +## Install the required version of the .NET Core SDK Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the latest version of the .NET Core SDK compatible with Avalonia UI. Make sure to download the SDK (not just the "runtime") package. The version compatible is indicated within the [global.json](https://github.com/AvaloniaUI/Avalonia/blob/master/global.json) file. Note that Avalonia UI does not always use the latest version and is hardcoded to use the last version known to be compatible (SDK releases may break the builds from time-to-time). -### Open in Visual Studio +## Build and Run Avalonia -Open the `Avalonia.sln` solution in Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application. +``` +cd samples\ControlCatalog.NetCore +dotnet restore +dotnet run +``` + +## Opening in Visual Studio -### Troubleshooting +If you want to open Avalonia in Visual Studio you have two options: + +- Avalonia.sln: This contains the whole of Avalonia in including desktop, mobile and web. You must have a number of dotnet workloads installed in order to build everything in this solution +- Avalonia.Desktop.slnf: This solution filter opens only the parts of Avalonia required to run on desktop. This requires no extra workloads to be installed. + +Avalonia requires Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. + +Build and run `ControlCatalog.NetCore` project to see the sample application. + +### Visual Studio Troubleshooting * **Error CS0006: Avalonia.DesktopRuntime.dll could not be found** @@ -35,16 +50,15 @@ It's *not* possible to build the *whole* project on Linux/macOS. You can only bu MonoDevelop, Xamarin Studio and Visual Studio for Mac aren't capable of properly opening our solution. You can use Rider (at least 2017.2 EAP) or VSCode instead. They will fail to load most of platform specific projects, but you don't need them to run on .NET Core. -### Install the latest version of the .NET Core SDK +## Install the latest version of the .NET Core SDK Go to https://www.microsoft.com/net/core and follow the instructions for your OS. Make sure to download the SDK (not just the "runtime") package. -### Additional requirements for macOS +## Additional requirements for macOS The build process needs [Xcode](https://developer.apple.com/xcode/) to build the native library. Following the install instructions at the [Xcode](https://developer.apple.com/xcode/) website to properly install. - -### Clone the Avalonia repository +## Clone the Avalonia repository ``` git clone https://github.com/AvaloniaUI/Avalonia.git @@ -52,7 +66,7 @@ cd Avalonia git submodule update --init --recursive ``` -### Build native libraries (macOS only) +## Build native libraries (macOS only) On macOS it is necessary to build and manually install the respective native libraries using [Xcode](https://developer.apple.com/xcode/). Execute the build script in the root project with the `CompileNative` task. It will build the headers, build the libraries, and place them in the appropriate place to allow .NET to find them at compilation and run time. @@ -60,7 +74,7 @@ On macOS it is necessary to build and manually install the respective native lib ./build.sh CompileNative ``` -### Build and Run Avalonia +## Build and Run Avalonia ``` cd samples/ControlCatalog.NetCore From 4160e9b19f52f4c7cdf13d91d124df6e9d93bb13 Mon Sep 17 00:00:00 2001 From: out4blood88 <96898225+out4blood88@users.noreply.github.com> Date: Sat, 5 Nov 2022 00:14:16 -0700 Subject: [PATCH 114/122] Update UniformGridLayoutState.cs Corrected implementation for UniformGridLayout ItemsStretch Fill and Uniform --- src/Avalonia.Base/Layout/UniformGridLayoutState.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Layout/UniformGridLayoutState.cs b/src/Avalonia.Base/Layout/UniformGridLayoutState.cs index d6b5a30bfc..65cdf4c94b 100644 --- a/src/Avalonia.Base/Layout/UniformGridLayoutState.cs +++ b/src/Avalonia.Base/Layout/UniformGridLayoutState.cs @@ -117,12 +117,12 @@ namespace Avalonia.Layout double extraMinorPixelsForEachItem = 0.0; if (!double.IsInfinity(availableSizeMinor)) { - var numItemsPerColumn = Math.Min( + var numItemsPerColumn = (int)Math.Min( maxItemsPerLine, Math.Max(1.0, availableSizeMinor / (itemSizeMinor + minorItemSpacing))); var usedSpace = (numItemsPerColumn * (itemSizeMinor + minorItemSpacing)) - minorItemSpacing; - var remainingSpace = ((int)(availableSizeMinor - usedSpace)); - extraMinorPixelsForEachItem = remainingSpace / ((int)numItemsPerColumn); + var remainingSpace = availableSizeMinor - usedSpace; + extraMinorPixelsForEachItem = (int)(remainingSpace / numItemsPerColumn); } if (stretch == UniformGridLayoutItemsStretch.Fill) From ea80be321f40d12660f440a14e449ac29636ce0b Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 6 Nov 2022 09:00:46 -0500 Subject: [PATCH 115/122] Add all material color palette defined colors --- .../ColorPalettes/MaterialColorPalette.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs index 8fa7ede77e..d009926bc5 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorPalettes/MaterialColorPalette.cs @@ -42,6 +42,11 @@ namespace Avalonia.Controls Red800 = 0xFFC62828, Red900 = 0xFFB71C1C, + RedA100 = 0xFFFF8A80, + RedA200 = 0xFFFF5252, + RedA400 = 0xFFFF1744, + RedA700 = 0xFFD50000, + // Pink Pink50 = 0xFFFCE4EC, Pink100 = 0xFFF8BBD0, @@ -54,6 +59,11 @@ namespace Avalonia.Controls Pink800 = 0xFFAD1457, Pink900 = 0xFF880E4F, + PinkA100 = 0xFFFF80AB, + PinkA200 = 0xFFFF4081, + PinkA400 = 0xFFF50057, + PinkA700 = 0xFFC51162, + // Purple Purple50 = 0xFFF3E5F5, Purple100 = 0xFFE1BEE7, @@ -66,6 +76,11 @@ namespace Avalonia.Controls Purple800 = 0xFF6A1B9A, Purple900 = 0xFF4A148C, + PurpleA100 = 0xFFEA80FC, + PurpleA200 = 0xFFE040FB, + PurpleA400 = 0xFFD500F9, + PurpleA700 = 0xFFAA00FF, + // Deep Purple DeepPurple50 = 0xFFEDE7F6, DeepPurple100 = 0xFFD1C4E9, @@ -78,6 +93,11 @@ namespace Avalonia.Controls DeepPurple800 = 0xFF4527A0, DeepPurple900 = 0xFF311B92, + DeepPurpleA100 = 0xFFB388FF, + DeepPurpleA200 = 0xFF7C4DFF, + DeepPurpleA400 = 0xFF651FFF, + DeepPurpleA700 = 0xFF6200EA, + // Indigo Indigo50 = 0xFFE8EAF6, Indigo100 = 0xFFC5CAE9, @@ -90,6 +110,11 @@ namespace Avalonia.Controls Indigo800 = 0xFF283593, Indigo900 = 0xFF1A237E, + IndigoA100 = 0xFF8C9EFF, + IndigoA200 = 0xFF536DFE, + IndigoA400 = 0xFF3D5AFE, + IndigoA700 = 0xFF304FFE, + // Blue Blue50 = 0xFFE3F2FD, Blue100 = 0xFFBBDEFB, @@ -102,6 +127,11 @@ namespace Avalonia.Controls Blue800 = 0xFF1565C0, Blue900 = 0xFF0D47A1, + BlueA100 = 0xFF82B1FF, + BlueA200 = 0xFF448AFF, + BlueA400 = 0xFF2979FF, + BlueA700 = 0xFF2962FF, + // Light Blue LightBlue50 = 0xFFE1F5FE, LightBlue100 = 0xFFB3E5FC, @@ -114,6 +144,11 @@ namespace Avalonia.Controls LightBlue800 = 0xFF0277BD, LightBlue900 = 0xFF01579B, + LightBlueA100 = 0xFF80D8FF, + LightBlueA200 = 0xFF40C4FF, + LightBlueA400 = 0xFF00B0FF, + LightBlueA700 = 0xFF0091EA, + // Cyan Cyan50 = 0xFFE0F7FA, Cyan100 = 0xFFB2EBF2, @@ -126,6 +161,11 @@ namespace Avalonia.Controls Cyan800 = 0xFF00838F, Cyan900 = 0xFF006064, + CyanA100 = 0xFF84FFFF, + CyanA200 = 0xFF18FFFF, + CyanA400 = 0xFF00E5FF, + CyanA700 = 0xFF00B8D4, + // Teal Teal50 = 0xFFE0F2F1, Teal100 = 0xFFB2DFDB, @@ -138,6 +178,11 @@ namespace Avalonia.Controls Teal800 = 0xFF00695C, Teal900 = 0xFF004D40, + TealA100 = 0xFFA7FFEB, + TealA200 = 0xFF64FFDA, + TealA400 = 0xFF1DE9B6, + TealA700 = 0xFF00BFA5, + // Green Green50 = 0xFFE8F5E9, Green100 = 0xFFC8E6C9, @@ -150,6 +195,11 @@ namespace Avalonia.Controls Green800 = 0xFF2E7D32, Green900 = 0xFF1B5E20, + GreenA100 = 0xFFB9F6CA, + GreenA200 = 0xFF69F0AE, + GreenA400 = 0xFF00E676, + GreenA700 = 0xFF00C853, + // Light Green LightGreen50 = 0xFFF1F8E9, LightGreen100 = 0xFFDCEDC8, @@ -162,6 +212,11 @@ namespace Avalonia.Controls LightGreen800 = 0xFF558B2F, LightGreen900 = 0xFF33691E, + LightGreenA100 = 0xFFCCFF90, + LightGreenA200 = 0xFFB2FF59, + LightGreenA400 = 0xFF76FF03, + LightGreenA700 = 0xFF64DD17, + // Lime Lime50 = 0xFFF9FBE7, Lime100 = 0xFFF0F4C3, @@ -174,6 +229,11 @@ namespace Avalonia.Controls Lime800 = 0xFF9E9D24, Lime900 = 0xFF827717, + LimeA100 = 0xFFF4FF81, + LimeA200 = 0xFFEEFF41, + LimeA400 = 0xFFC6FF00, + LimeA700 = 0xFFAEEA00, + // Yellow Yellow50 = 0xFFFFFDE7, Yellow100 = 0xFFFFF9C4, @@ -186,6 +246,11 @@ namespace Avalonia.Controls Yellow800 = 0xFFF9A825, Yellow900 = 0xFFF57F17, + YellowA100 = 0xFFFFFF8D, + YellowA200 = 0xFFFFFF00, + YellowA400 = 0xFFFFEA00, + YellowA700 = 0xFFFFD600, + // Amber Amber50 = 0xFFFFF8E1, Amber100 = 0xFFFFECB3, @@ -198,6 +263,11 @@ namespace Avalonia.Controls Amber800 = 0xFFFF8F00, Amber900 = 0xFFFF6F00, + AmberA100 = 0xFFFFE57F, + AmberA200 = 0xFFFFD740, + AmberA400 = 0xFFFFC400, + AmberA700 = 0xFFFFAB00, + // Orange Orange50 = 0xFFFFF3E0, Orange100 = 0xFFFFE0B2, @@ -210,6 +280,11 @@ namespace Avalonia.Controls Orange800 = 0xFFEF6C00, Orange900 = 0xFFE65100, + OrangeA100 = 0xFFFFD180, + OrangeA200 = 0xFFFFAB40, + OrangeA400 = 0xFFFF9100, + OrangeA700 = 0xFFFF6D00, + // Deep Orange DeepOrange50 = 0xFFFBE9E7, DeepOrange100 = 0xFFFFCCBC, @@ -222,6 +297,11 @@ namespace Avalonia.Controls DeepOrange800 = 0xFFD84315, DeepOrange900 = 0xFFBF360C, + DeepOrangeA100 = 0xFFFF9E80, + DeepOrangeA200 = 0xFFFF6E40, + DeepOrangeA400 = 0xFFFF3D00, + DeepOrangeA700 = 0xFFDD2C00, + // Brown Brown50 = 0xFFEFEBE9, Brown100 = 0xFFD7CCC8, @@ -257,6 +337,9 @@ namespace Avalonia.Controls BlueGray700 = 0xFF455A64, BlueGray800 = 0xFF37474F, BlueGray900 = 0xFF263238, + + Black = 0xFF000000, + White = 0xFFFFFFFF, } // See: https://material.io/design/color/the-color-system.html#tools-for-picking-colors From 84c04bcfeb3f25805b656fe1e70eae90ad3e0339 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 8 Nov 2022 00:08:56 +0600 Subject: [PATCH 116/122] [OSX] Fixed multi-window freeze on Show when another window is rendering --- native/Avalonia.Native/src/OSX/rendertarget.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 2075cc85ab..1c22c91207 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -183,8 +183,11 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta [_layer setContents: (__bridge IOSurface*) surface->surface]; } [CATransaction commit]; - [CATransaction flush]; } + // This can trigger event processing on the main thread + // which might need to lock the renderer + // which can cause a deadlock. So flush call is outside of the lock + [CATransaction flush]; } else dispatch_async(dispatch_get_main_queue(), ^{ From 562d687daab221ef8666e0c9bab9127006d1eebd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 7 Nov 2022 22:58:47 +0300 Subject: [PATCH 117/122] Use 7.0.100 SDK --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index dc6da556b3..a9318b212f 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.100-rc.2.22477.23", + "version": "7.0.100", "rollForward": "latestFeature" }, "msbuild-sdks": { From b5cf18bd4d148809918ff68cb263fa87c6d603dd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 7 Nov 2022 23:01:29 +0300 Subject: [PATCH 118/122] Install 7.0.100 --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 903f9e3843..ee78a79e89 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,7 +37,7 @@ jobs: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.2.22477.23 + version: 7.0.100 - task: CmdLine@2 displayName: 'Install Workloads' @@ -74,7 +74,7 @@ jobs: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.2.22477.23 + version: 7.0.100 - task: CmdLine@2 displayName: 'Install Workloads' @@ -145,7 +145,7 @@ jobs: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.2.22477.23 + version: 7.0.100 - task: CmdLine@2 displayName: 'Install Workloads' From 6c32c97ec5f5871507691f1439fbcb8483cdb9f2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 7 Nov 2022 23:04:35 +0300 Subject: [PATCH 119/122] Update azure-pipelines.yml --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ee78a79e89..a3bbc33418 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -35,7 +35,7 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + displayName: 'Use .NET Core SDK 7.0' inputs: version: 7.0.100 @@ -72,7 +72,7 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + displayName: 'Use .NET Core SDK 7.0.100' inputs: version: 7.0.100 @@ -143,7 +143,7 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + displayName: 'Use .NET Core SDK 7.0.100' inputs: version: 7.0.100 From a52696bcd6efbdc74e294ab41a267a4092bd9e53 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 8 Nov 2022 13:41:08 -0500 Subject: [PATCH 120/122] Update Microsoft.Build.Framework --- nukebuild/_build.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 865d935ad7..efad0d69f4 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 9003dfde5164f5ba7ca47b87fbc6827e8c7bb554 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 8 Nov 2022 13:52:43 -0500 Subject: [PATCH 121/122] Parse target framework manually --- nukebuild/Build.cs | 19 ++++++++++++++++++- nukebuild/_build.csproj | 3 +-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 7425c344c3..2295c0beda 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -145,8 +145,25 @@ partial class Build : NukeBuild { Information($"Running tests from {projectName}"); var project = Solution.GetProject(projectName).NotNull("project != null"); + // Nuke and MSBuild tools have build-in helpers to get target frameworks from the project. + // Unfortunately, it gets broken with every second SDK update, so we had to do it manually. + var fileXml = XDocument.Parse(File.ReadAllText(project.Path)); + var targetFrameworks = fileXml.Descendants("TargetFrameworks") + .FirstOrDefault()?.Value.Split(';').Select(f => f.Trim()); + if (targetFrameworks is null) + { + var targetFramework = fileXml.Descendants("TargetFramework").FirstOrDefault()?.Value; + if (targetFramework is not null) + { + targetFrameworks = new[] { targetFramework }; + } + } + if (targetFrameworks is null) + { + throw new InvalidOperationException("No target frameworks were found in the test project"); + } - foreach (var fw in project.GetTargetFrameworks()) + foreach (var fw in targetFrameworks) { if (fw.StartsWith("net4") && RuntimeInformation.IsOSPlatform(OSPlatform.Linux) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index efad0d69f4..591c72445b 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -16,8 +16,7 @@ - - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 6b080eabe02588d3cd28e7c7b389ba7c40b70197 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 8 Nov 2022 14:28:16 -0500 Subject: [PATCH 122/122] Restore Microsoft.Build.Framework dependency --- nukebuild/_build.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 591c72445b..92d9732e91 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -17,6 +17,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive