From ba2747b897204d3c7389adbb7ddf97928ce1bf7d Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 7 Jul 2022 10:45:37 +0200 Subject: [PATCH 001/453] feat: StringBuilderCache --- src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Avalonia.Base/Input/KeyGesture.cs | 4 +- src/Avalonia.Base/Logging/TraceLogSink.cs | 6 +- src/Avalonia.Base/Media/BoxShadow.cs | 4 +- src/Avalonia.Base/Media/BoxShadows.cs | 4 +- .../Media/Fonts/FamilyNameCollection.cs | 4 +- src/Avalonia.Base/Media/HslColor.cs | 4 +- src/Avalonia.Base/Media/HsvColor.cs | 4 +- src/Avalonia.Base/StringBuilderCache.cs | 68 +++++++++++++++++++ src/Avalonia.Base/Styling/NthChildSelector.cs | 5 +- .../Styling/PropertyEqualsSelector.cs | 4 +- .../Styling/TypeNameAndClassSelector.cs | 4 +- .../Avalonia.Build.Tasks.csproj | 1 + .../Helpers/ColorHelper.cs | 4 +- src/Avalonia.Controls.DataGrid/DataGrid.cs | 8 +-- .../Converters/PlatformKeyGestureConverter.cs | 8 +-- .../Documents/InlineCollection.cs | 4 +- .../Diagnostics/VisualTreeDebug.cs | 2 +- .../Avalonia.Markup.Xaml.Loader.csproj | 3 + .../Avalonia.Win32/ClipboardFormats.cs | 4 +- .../Input/WindowsKeyboardDevice.cs | 4 +- src/Windows/Avalonia.Win32/OleDataObject.cs | 4 +- .../Avalonia.Designer.HostApp.csproj | 1 + 23 files changed, 115 insertions(+), 40 deletions(-) create mode 100644 src/Avalonia.Base/StringBuilderCache.cs diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index a07e0e3667..0018d40f66 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Avalonia.Base/Input/KeyGesture.cs b/src/Avalonia.Base/Input/KeyGesture.cs index 3b7a828b86..2123886cb1 100644 --- a/src/Avalonia.Base/Input/KeyGesture.cs +++ b/src/Avalonia.Base/Input/KeyGesture.cs @@ -106,7 +106,7 @@ namespace Avalonia.Input public override string ToString() { - var s = new StringBuilder(); + var s = StringBuilderCache.Acquire(); static void Plus(StringBuilder s) { @@ -142,7 +142,7 @@ namespace Avalonia.Input Plus(s); s.Append(Key); - return s.ToString(); + return StringBuilderCache.GetStringAndRelease(s); } public bool Matches(KeyEventArgs keyEvent) => diff --git a/src/Avalonia.Base/Logging/TraceLogSink.cs b/src/Avalonia.Base/Logging/TraceLogSink.cs index 05e4b8bc5a..fc3897fade 100644 --- a/src/Avalonia.Base/Logging/TraceLogSink.cs +++ b/src/Avalonia.Base/Logging/TraceLogSink.cs @@ -46,7 +46,7 @@ namespace Avalonia.Logging object? source, object?[]? values) { - var result = new StringBuilder(template.Length); + var result = StringBuilderCache.Acquire(template.Length); var r = new CharacterReader(template.AsSpan()); var i = 0; @@ -89,7 +89,7 @@ namespace Avalonia.Logging result.Append(')'); } - return result.ToString(); + return StringBuilderCache.GetStringAndRelease(result); } private static string Format( @@ -98,7 +98,7 @@ namespace Avalonia.Logging object? source, object?[] v) { - var result = new StringBuilder(template.Length); + var result = StringBuilderCache.Acquire(template.Length); var r = new CharacterReader(template.AsSpan()); var i = 0; diff --git a/src/Avalonia.Base/Media/BoxShadow.cs b/src/Avalonia.Base/Media/BoxShadow.cs index b01f59f5f8..cc97d89cfc 100644 --- a/src/Avalonia.Base/Media/BoxShadow.cs +++ b/src/Avalonia.Base/Media/BoxShadow.cs @@ -80,7 +80,7 @@ namespace Avalonia.Media public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); if (IsEmpty) { @@ -114,7 +114,7 @@ namespace Avalonia.Media sb.AppendFormat(" {0}", Color.ToString()); - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } public static unsafe BoxShadow Parse(string s) diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index 4614ea4e3c..44288d89cf 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/src/Avalonia.Base/Media/BoxShadows.cs @@ -45,7 +45,7 @@ namespace Avalonia.Media public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); if (Count == 0) { @@ -57,7 +57,7 @@ namespace Avalonia.Media sb.AppendFormat("{0} ", boxShadow.ToString()); } - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } diff --git a/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs b/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs index 99daaf2143..eb42f6443b 100644 --- a/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs +++ b/src/Avalonia.Base/Media/Fonts/FamilyNameCollection.cs @@ -77,7 +77,7 @@ namespace Avalonia.Media.Fonts /// public override string ToString() { - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); for (var index = 0; index < Names.Count; index++) { @@ -91,7 +91,7 @@ namespace Avalonia.Media.Fonts builder.Append(", "); } - return builder.ToString(); + return StringBuilderCache.GetStringAndRelease(builder); } /// diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index e8a4d6f94f..485bb1db16 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -202,7 +202,7 @@ namespace Avalonia.Media /// public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); // Use a format similar to CSS. However: // - To ensure precision is never lost, allow decimal places. @@ -225,7 +225,7 @@ namespace Avalonia.Media sb.Append(A.ToString(CultureInfo.InvariantCulture)); sb.Append(')'); - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } /// diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 924ef4778b..512e57ae07 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -202,7 +202,7 @@ namespace Avalonia.Media /// public override string ToString() { - var sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); // Use a format similar to CSS. However: // - To ensure precision is never lost, allow decimal places. @@ -225,7 +225,7 @@ namespace Avalonia.Media sb.Append(A.ToString(CultureInfo.InvariantCulture)); sb.Append(')'); - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); } /// diff --git a/src/Avalonia.Base/StringBuilderCache.cs b/src/Avalonia.Base/StringBuilderCache.cs new file mode 100644 index 0000000000..060d76090a --- /dev/null +++ b/src/Avalonia.Base/StringBuilderCache.cs @@ -0,0 +1,68 @@ +// This file is imported from dotnet/runtime +// Source Link: https://github.com/dotnet/runtime/blob/e63d21947e734db2da5093510a6636b5b7fb45b5/src/libraries/Common/src/System/Text/StringBuilderCache.cs +// Commit: a9c5ead on Feb 10, 2021, https://github.com/dotnet/runtime/commit/a9c5eadd951dcba73167f72cc624eb790573663a +// +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Text; + +namespace Avalonia; + +// Provide a cached reusable instance of stringbuilder per thread. +internal static class StringBuilderCache +{ + // The value 360 was chosen in discussion with performance experts as a compromise between using + // as little memory per thread as possible and still covering a large part of short-lived + // StringBuilder creations on the startup path of VS designers. + internal const int MaxBuilderSize = 360; + private const int DefaultCapacity = 16; // == StringBuilder.DefaultCapacity + + // WARNING: We allow diagnostic tools to directly inspect this member (t_cachedInstance). + // See https://github.com/dotnet/corert/blob/master/Documentation/design-docs/diagnostics/diagnostics-tools-contract.md for more details. + // Please do not change the type, the name, or the semantic usage of this member without understanding the implication for tools. + // Get in touch with the diagnostics team if you have questions. + [ThreadStatic] + private static StringBuilder? t_cachedInstance; + + /// Get a StringBuilder for the specified capacity. + /// If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied. + public static StringBuilder Acquire(int capacity = DefaultCapacity) + { + if (capacity <= MaxBuilderSize) + { + StringBuilder? sb = t_cachedInstance; + if (sb != null) + { + // Avoid stringbuilder block fragmentation by getting a new StringBuilder + // when the requested size is larger than the current capacity + if (capacity <= sb.Capacity) + { + t_cachedInstance = null; + sb.Clear(); + return sb; + } + } + } + + return new StringBuilder(capacity); + } + + /// Place the specified builder in the cache if it is not too big. + public static void Release(StringBuilder sb) + { + if (sb.Capacity <= MaxBuilderSize) + { + t_cachedInstance = sb; + } + } + + /// ToString() the stringbuilder, Release it to the cache, and return the resulting string. + public static string GetStringAndRelease(StringBuilder sb) + { + string result = sb.ToString(); + Release(sb); + return result; + } +} diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index 047bf434da..a7af27f4bf 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -110,7 +110,8 @@ namespace Avalonia.Styling public override string ToString() { var expectedCapacity = NthLastChildSelectorName.Length + 8; - var stringBuilder = new StringBuilder(_previous?.ToString(), expectedCapacity); + var stringBuilder = StringBuilderCache.Acquire(expectedCapacity); + stringBuilder.Append(_previous?.ToString()); stringBuilder.Append(':'); stringBuilder.Append(_reversed ? NthLastChildSelectorName : NthChildSelectorName); @@ -140,7 +141,7 @@ namespace Avalonia.Styling stringBuilder.Append(')'); - return stringBuilder.ToString(); + return StringBuilderCache.GetStringAndRelease(stringBuilder); } } } diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 7a37daf087..6663ed8887 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -42,7 +42,7 @@ namespace Avalonia.Styling { if (_selectorString == null) { - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); if (_previous != null) { @@ -67,7 +67,7 @@ namespace Avalonia.Styling builder.Append(_value ?? string.Empty); builder.Append(']'); - _selectorString = builder.ToString(); + _selectorString = StringBuilderCache.GetStringAndRelease(builder); } return _selectorString; diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 24d5d6bbbf..5f004e91df 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -144,7 +144,7 @@ namespace Avalonia.Styling private string BuildSelectorString() { - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); if (_previous != null) { @@ -184,7 +184,7 @@ namespace Avalonia.Styling } } - return builder.ToString(); + return StringBuilderCache.GetStringAndRelease(builder); } } } diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index a801d338c3..1d717d5694 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -50,6 +50,7 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) + Markup/%(RecursiveDir)%(FileName)%(Extension) diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs index 32a898ee71..38fa58e7bb 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs @@ -109,7 +109,7 @@ namespace Avalonia.Controls.Primitives // Cache results for next time as well if (closestKnownColor != KnownColor.None) { - StringBuilder sb = new StringBuilder(); + var sb = StringBuilderCache.Acquire(); string name = closestKnownColor.ToString(); // Add spaces converting PascalCase to human-readable names @@ -124,7 +124,7 @@ namespace Avalonia.Controls.Primitives sb.Append(name[i]); } - string displayName = sb.ToString(); + string displayName = StringBuilderCache.GetStringAndRelease(sb); lock (cacheMutex) { diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index d42468f47e..554b1c371b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -5990,7 +5990,7 @@ namespace Avalonia.Controls /// The formatted string. private string FormatClipboardContent(DataGridRowClipboardEventArgs e) { - var text = new StringBuilder(); + var text = StringBuilderCache.Acquire(); var clipboardRowContent = e.ClipboardRowContent; var numberOfItem = clipboardRowContent.Count; for (int cellIndex = 0; cellIndex < numberOfItem; cellIndex++) @@ -6007,7 +6007,7 @@ namespace Avalonia.Controls text.Append('\n'); } } - return text.ToString(); + return StringBuilderCache.GetStringAndRelease(text); } /// @@ -6022,7 +6022,7 @@ namespace Avalonia.Controls if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0) { - StringBuilder textBuilder = new StringBuilder(); + var textBuilder = StringBuilderCache.Acquire(); if (ClipboardCopyMode == DataGridClipboardCopyMode.IncludeHeader) { @@ -6048,7 +6048,7 @@ namespace Avalonia.Controls textBuilder.Append(FormatClipboardContent(itemArgs)); } - string text = textBuilder.ToString(); + string text = StringBuilderCache.GetStringAndRelease(textBuilder); if (!string.IsNullOrEmpty(text)) { diff --git a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs index 9a657cce68..47c2f94e18 100644 --- a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs +++ b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs @@ -62,7 +62,7 @@ namespace Avalonia.Controls.Converters private static string ToString(KeyGesture gesture, string meta) { - var s = new StringBuilder(); + var s = StringBuilderCache.Acquire(); static void Plus(StringBuilder s) { @@ -98,12 +98,12 @@ namespace Avalonia.Controls.Converters Plus(s); s.Append(ToString(gesture.Key)); - return s.ToString(); + return StringBuilderCache.GetStringAndRelease(s); } private static string ToOSXString(KeyGesture gesture) { - var s = new StringBuilder(); + var s = StringBuilderCache.Acquire(); if (gesture.KeyModifiers.HasAllFlags(KeyModifiers.Control)) { @@ -127,7 +127,7 @@ namespace Avalonia.Controls.Converters s.Append(ToOSXString(gesture.Key)); - return s.ToString(); + return StringBuilderCache.GetStringAndRelease(s); } private static string ToString(Key key) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index dc688fc359..11225a87a1 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -78,14 +78,14 @@ namespace Avalonia.Controls.Documents return _text; } - var builder = new StringBuilder(); + var builder = StringBuilderCache.Acquire(); foreach (var inline in this) { inline.AppendText(builder); } - return builder.ToString(); + return StringBuilderCache.GetStringAndRelease(builder); } set { diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs index 4adcd32302..d1f871d76f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs @@ -10,7 +10,7 @@ namespace Avalonia.Diagnostics { public static string PrintVisualTree(IVisual visual) { - StringBuilder result = new StringBuilder(); + var result = new StringBuilder(); PrintVisualTree(visual, result, 0); return result.ToString(); } 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 b89ea8399a..0b6b77e540 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 @@ -7,6 +7,9 @@ $(DefineConstants);XAMLX_INTERNAL + + + diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs index 7538dedfca..f5b8cd6b96 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormats.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -35,9 +35,9 @@ namespace Avalonia.Win32 private static string QueryFormatName(ushort format) { - StringBuilder sb = new StringBuilder(MAX_FORMAT_NAME_LENGTH); + var sb = StringBuilderCache.Acquire(MAX_FORMAT_NAME_LENGTH); if (UnmanagedMethods.GetClipboardFormatName(format, sb, sb.Capacity) > 0) - return sb.ToString(); + return StringBuilderCache.GetStringAndRelease(sb); return null; } diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs index 1258bb0109..878011b5aa 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs @@ -49,7 +49,7 @@ namespace Avalonia.Win32.Input public string StringFromVirtualKey(uint virtualKey) { - StringBuilder result = new StringBuilder(256); + var result = StringBuilderCache.Acquire(256); int length = UnmanagedMethods.ToUnicode( virtualKey, 0, @@ -57,7 +57,7 @@ namespace Avalonia.Win32.Input result, 256, 0); - return result.ToString(); + return StringBuilderCache.GetStringAndRelease(result); } private void UpdateKeyStates() diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index ba17177473..837b21e34f 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -103,11 +103,11 @@ namespace Avalonia.Win32 for (int i = 0; i < fileCount; i++) { int pathLen = UnmanagedMethods.DragQueryFile(hGlobal, i, null, 0); - StringBuilder sb = new StringBuilder(pathLen+1); + var sb = StringBuilderCache.Acquire(pathLen+1); if (UnmanagedMethods.DragQueryFile(hGlobal, i, sb, sb.Capacity) == pathLen) { - files.Add(sb.ToString()); + files.Add(StringBuilderCache.GetStringAndRelease(sb)); } } } diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 1cf68c1605..3dfef234a9 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -16,6 +16,7 @@ + From 94ecd84c8f24d210f537c495e044ded266f1147b Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 18 Jul 2022 11:25:13 +0200 Subject: [PATCH 002/453] feat(LibInputBackend): minimal linux boot with no input --- .../Input/LibInput/LibInputBackend.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 15d42789d4..6e26794b24 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { public class LibInputBackend : IInputBackend { + private const string LibInput = nameof(Logging.LogArea.X11Platform) + "/" + nameof(LibInput); private IScreenInfoProvider _screen; private IInputRoot _inputRoot; private readonly Queue _inputThreadActions = new Queue(); @@ -29,15 +28,21 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput new Thread(()=>InputThread(ctx)).Start(); } - - private unsafe void InputThread(IntPtr ctx) { var fd = libinput_get_fd(ctx); var timeval = stackalloc IntPtr[2]; - + if (!Directory.Exists("/dev/input")) + { + if (Logging.Logger.IsEnabled(Logging.LogEventLevel.Warning,LibInput)) + { + Logging.Logger.TryGet(Logging.LogEventLevel.Warning, LibInput) + ?.Log(this, "Not connect any input device."); + } + return; + } foreach (var f in Directory.GetFiles("/dev/input", "event*")) libinput_path_add_device(ctx, f); while (true) From e5241a59e707a7cba8e7c106c36f4a6ead9d1469 Mon Sep 17 00:00:00 2001 From: Steven He Date: Fri, 22 Jul 2022 13:16:38 +0900 Subject: [PATCH 003/453] various fixes --- src/Avalonia.Base/Visual.cs | 10 ++++++++-- src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 8feba116f0..fbc940114a 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -667,8 +667,14 @@ namespace Avalonia if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { - var root = this.FindAncestorOfType() ?? - throw new AvaloniaInternalException("Visual is atached to visual tree but root could not be found."); + var root = this.FindAncestorOfType(); + if (root is null) + { + Logger.TryGet(LogEventLevel.Error, "Visual")?.Log("Visual", + "Visual is atached to visual tree but root could not be found."); + return; + } + var e = new VisualTreeAttachmentEventArgs(_visualParent, root); OnAttachedToVisualTreeCore(e); } diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs index b3469c212b..ccdc5ac19b 100644 --- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs +++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs @@ -148,6 +148,13 @@ namespace Avalonia.OpenGL.Controls return false; } + if (_context == null) + { + Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase", + "Unable to initialize OpenGL: unable to create additional OpenGL context."); + return false; + } + GlVersion = _context.Version; try { From 0bdd0c3160d73a35982ed5bcb7fc421a3d7bd381 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 15:24:20 +0100 Subject: [PATCH 004/453] add a failing integration test. --- .../WindowTests.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 2b10c302bc..9e39d0ae58 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,7 +1,9 @@ using System; +using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; +using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; using Xunit; @@ -55,6 +57,43 @@ namespace Avalonia.IntegrationTests.Appium } } } + + [PlatformFact(TestPlatforms.Windows)] + public void OnWindows_Docked_Windows_Retain_Size_Position_When_Restored() + { + using (OpenWindow(new Size(400, 400), ShowWindowMode.NonOwned, WindowStartupLocation.Manual)) + { + var windowState = _session.FindElementByAccessibilityId("WindowState"); + + Assert.Equal("Normal", windowState.GetComboBoxValue()); + + + var window = _session.FindElements(By.XPath("//Window")).First(); + + new Actions(_session) + .KeyDown(Keys.Meta) + .SendKeys(Keys.Left) + .KeyUp(Keys.Meta) + .Perform(); + + var original = GetWindowInfo(); + + windowState.Click(); + _session.FindElementByName("Minimized").SendClick(); + + new Actions(_session) + .KeyDown(Keys.Alt) + .SendKeys(Keys.Tab) + .KeyUp(Keys.Alt) + .Perform(); + + var current = GetWindowInfo(); + + Assert.Equal(original.Position, current.Position); + Assert.Equal(original.FrameSize, current.FrameSize); + + } + } [Theory] From cdc3100097a48f5bc1a77d483d2ffdd6ad2718ae Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 15:24:39 +0100 Subject: [PATCH 005/453] add fix to make code path consistent with wpf. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 2f1a116af7..ce8374a68f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -285,7 +285,7 @@ namespace Avalonia.Win32 set { - if (IsWindowVisible(_hwnd)) + if (IsWindowVisible(_hwnd) && _lastWindowState != value) { ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated } From 8c39dd54a08f9ea3bafac190b99cd29cac3351ca Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 16:02:56 +0100 Subject: [PATCH 006/453] fix exiting fullscreen. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ce8374a68f..e990efafdb 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -290,6 +290,7 @@ namespace Avalonia.Win32 ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated } + _lastWindowState = value; _showWindowState = value; } } From 5fcc5ecfea3e539e37f497ff8d11602fadfd69ca Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 22 Jul 2022 17:11:34 +0100 Subject: [PATCH 007/453] disable broken tests. --- tests/Avalonia.IntegrationTests.Appium/MenuTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index d1d231466f..b0d395464b 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -57,7 +57,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - [PlatformFact(TestPlatforms.Windows)] + /*[PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Arrow_Keys() { new Actions(_session) @@ -103,7 +103,7 @@ namespace Avalonia.IntegrationTests.Appium var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem"); Assert.Equal("_Grandchild", clickedMenuItem.Text); - } + }*/ [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Click_Arrow_Keys() From a9cc499de3663600c9e8b9bce2646af3af659621 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 27 Jul 2022 10:04:32 +0100 Subject: [PATCH 008/453] Revert "disable broken tests." This reverts commit 5fcc5ecfea3e539e37f497ff8d11602fadfd69ca. --- tests/Avalonia.IntegrationTests.Appium/MenuTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index b0d395464b..d1d231466f 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -57,7 +57,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - /*[PlatformFact(TestPlatforms.Windows)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Arrow_Keys() { new Actions(_session) @@ -103,7 +103,7 @@ namespace Avalonia.IntegrationTests.Appium var clickedMenuItem = _session.FindElementByAccessibilityId("ClickedMenuItem"); Assert.Equal("_Grandchild", clickedMenuItem.Text); - }*/ + } [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Click_Arrow_Keys() From eb8ddef4122fe59e54123e125282a13bcfe822fd Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jul 2022 10:50:20 +0200 Subject: [PATCH 009/453] Add failing integration test on macOS. Hiding a child window, and then clicking on the parent window causes the child window to be erroneously re-shown. --- .../IntegrationTestApp/MainWindow.axaml.cs | 1 + .../IntegrationTestApp/ShowWindowTest.axaml | 3 +- .../WindowTests_MacOS.cs | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 9e180b12c5..1dab74dc9e 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -99,6 +99,7 @@ namespace IntegrationTestApp foreach (var window in lifetime.Windows) { + window.Show(); if (window.WindowState == WindowState.Minimized) window.WindowState = WindowState.Normal; } diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index 40c1642e91..4001bac7e2 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -3,7 +3,7 @@ x:Class="IntegrationTestApp.ShowWindowTest" Name="SecondaryWindow" Title="Show Window Test"> - + @@ -31,5 +31,6 @@ Maximized Fullscreen + diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index ecbdd5bade..4e5344dd25 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -211,6 +211,35 @@ namespace Avalonia.IntegrationTests.Appium } } + [PlatformFact(TestPlatforms.MacOS)] + public void Hidden_Child_Window_Is_Not_Reshown_When_Parent_Clicked() + { + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); + + // We don't use dispose to close the window here, because it seems that hiding and re-showing a window + // causes Appium to think it's a different window. + OpenWindow(null, ShowWindowMode.Owned, WindowStartupLocation.Manual); + + var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var hideButton = secondaryWindow.FindElementByAccessibilityId("HideButton"); + + hideButton.Click(); + + var windows = _session.FindElementsByXPath("XCUIElementTypeWindow"); + Assert.Single(windows); + + mainWindow.Click(); + + windows = _session.FindElementsByXPath("XCUIElementTypeWindow"); + Assert.Single(windows); + + _session.FindElementByAccessibilityId("RestoreAll").Click(); + + // Close the window manually. + secondaryWindow = FindWindow(_session, "SecondaryWindow"); + secondaryWindow.GetChromeButtons().close.Click(); + } + private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); From 9d356894bca636e1e1789c456e2440f08c119a18 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 28 Jul 2022 11:04:33 +0200 Subject: [PATCH 010/453] macOS: Don't bring invisible window to front. Check that a window is visible before bringing it to front, as bringing to front also shows the window. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 95f61422cb..af8c53cb33 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -121,7 +121,7 @@ void WindowImpl::BringToFront() { if(Window != nullptr) { - if (![Window isMiniaturized]) + if ([Window isVisible] && ![Window isMiniaturized]) { if(IsDialog()) { From 01e7bd2a523c2f119d1ccace9975de10e103c5d0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 1 Aug 2022 09:51:58 +0200 Subject: [PATCH 011/453] fix: move StringBuilderCache to Avalonia.Utilities --- src/Avalonia.Base/Media/BoxShadows.cs | 2 +- src/Avalonia.Base/Styling/NthChildSelector.cs | 2 +- src/Avalonia.Base/Styling/PropertyEqualsSelector.cs | 2 +- src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs | 2 +- src/Avalonia.Base/{ => Utilities}/StringBuilderCache.cs | 2 +- src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj | 2 +- src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs | 2 +- src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs | 1 + src/Avalonia.Controls/Documents/InlineCollection.cs | 2 +- src/Windows/Avalonia.Win32/ClipboardFormats.cs | 2 +- src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs | 2 +- src/Windows/Avalonia.Win32/OleDataObject.cs | 2 +- .../Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) rename src/Avalonia.Base/{ => Utilities}/StringBuilderCache.cs (98%) diff --git a/src/Avalonia.Base/Media/BoxShadows.cs b/src/Avalonia.Base/Media/BoxShadows.cs index 44288d89cf..ab2694389f 100644 --- a/src/Avalonia.Base/Media/BoxShadows.cs +++ b/src/Avalonia.Base/Media/BoxShadows.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel; -using System.Text; using Avalonia.Animation.Animators; +using Avalonia.Utilities; namespace Avalonia.Media { diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index a7ba79696e..c872a40ad4 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -1,8 +1,8 @@ #nullable enable using System; -using System.Text; using Avalonia.LogicalTree; using Avalonia.Styling.Activators; +using Avalonia.Utilities; namespace Avalonia.Styling { diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 8922b939f7..e98ff3f9c9 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -1,8 +1,8 @@ using System; using System.ComponentModel; using System.Globalization; -using System.Text; using Avalonia.Styling.Activators; +using Avalonia.Utilities; #nullable enable diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 698d96d0aa..1833f0d133 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Text; using Avalonia.Controls; using Avalonia.Styling.Activators; +using Avalonia.Utilities; #nullable enable diff --git a/src/Avalonia.Base/StringBuilderCache.cs b/src/Avalonia.Base/Utilities/StringBuilderCache.cs similarity index 98% rename from src/Avalonia.Base/StringBuilderCache.cs rename to src/Avalonia.Base/Utilities/StringBuilderCache.cs index 060d76090a..be8b24c848 100644 --- a/src/Avalonia.Base/StringBuilderCache.cs +++ b/src/Avalonia.Base/Utilities/StringBuilderCache.cs @@ -8,7 +8,7 @@ using System; using System.Text; -namespace Avalonia; +namespace Avalonia.Utilities; // Provide a cached reusable instance of stringbuilder per thread. internal static class StringBuilderCache diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index a5e38a86a2..7e1cb76911 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -50,7 +50,7 @@ Markup/%(RecursiveDir)%(FileName)%(Extension) - + Markup/%(RecursiveDir)%(FileName)%(Extension) diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs index 68aa30ea7a..c1a03b1b77 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorHelper.cs @@ -2,7 +2,7 @@ using System.Globalization; using System.Collections.Generic; using Avalonia.Media; -using System.Text; +using Avalonia.Utilities; namespace Avalonia.Controls.Primitives { diff --git a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs index 47c2f94e18..0fa43809ac 100644 --- a/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs +++ b/src/Avalonia.Controls/Converters/PlatformKeyGestureConverter.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using System.Text; using Avalonia.Data.Converters; using Avalonia.Input; +using Avalonia.Utilities; namespace Avalonia.Controls.Converters { diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 81ccff4cbd..190373169b 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -1,8 +1,8 @@ using System; -using System.Text; using Avalonia.Collections; using Avalonia.LogicalTree; using Avalonia.Metadata; +using Avalonia.Utilities; namespace Avalonia.Controls.Documents { diff --git a/src/Windows/Avalonia.Win32/ClipboardFormats.cs b/src/Windows/Avalonia.Win32/ClipboardFormats.cs index f5b8cd6b96..7bd7765f8c 100644 --- a/src/Windows/Avalonia.Win32/ClipboardFormats.cs +++ b/src/Windows/Avalonia.Win32/ClipboardFormats.cs @@ -2,9 +2,9 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Text; using Avalonia.Input; using Avalonia.Win32.Interop; +using Avalonia.Utilities; namespace Avalonia.Win32 { diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs index 878011b5aa..7e1e22579b 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs @@ -1,6 +1,6 @@ -using System.Text; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.Utilities; using Avalonia.Win32.Interop; namespace Avalonia.Win32.Input diff --git a/src/Windows/Avalonia.Win32/OleDataObject.cs b/src/Windows/Avalonia.Win32/OleDataObject.cs index 837b21e34f..f7345b3ff7 100644 --- a/src/Windows/Avalonia.Win32/OleDataObject.cs +++ b/src/Windows/Avalonia.Win32/OleDataObject.cs @@ -6,9 +6,9 @@ using System.Linq; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Serialization.Formatters.Binary; -using System.Text; using Avalonia.Input; using Avalonia.MicroCom; +using Avalonia.Utilities; using Avalonia.Win32.Interop; using IDataObject = Avalonia.Input.IDataObject; diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 3dfef234a9..3293066dd1 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -16,7 +16,7 @@ - + From 3fb4e926430f275c1fd7ac03644b35a7fe0fcb79 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 1 Aug 2022 10:00:24 +0200 Subject: [PATCH 012/453] fix: PrintVisualTree using StringBuilderCache --- src/Avalonia.Base/Avalonia.Base.csproj | 1 + src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index f722330db6..ede79c2943 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs index d1f871d76f..13c8e070e8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualTreeDebug.cs @@ -2,6 +2,7 @@ using System; using System.Text; using Avalonia.Controls; using Avalonia.Data; +using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Diagnostics @@ -10,9 +11,9 @@ namespace Avalonia.Diagnostics { public static string PrintVisualTree(IVisual visual) { - var result = new StringBuilder(); + var result = StringBuilderCache.Acquire(); PrintVisualTree(visual, result, 0); - return result.ToString(); + return StringBuilderCache.GetStringAndRelease(result); } private static void PrintVisualTree(IVisual visual, StringBuilder builder, int indent) From 3db08330124ecee997b129dca0e77dbffa173f56 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 1 Aug 2022 10:20:13 +0200 Subject: [PATCH 013/453] fix: Avalonia.Markup.Xaml.Loader --- .../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 0b6b77e540..ce931b9c14 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 @@ -8,7 +8,7 @@ - + From ef7ecb7c065719349f9c38b29001c29005f4d86f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 10:35:53 +0200 Subject: [PATCH 014/453] Revert "feat(LibInputBackend): minimal linux boot with no input" This reverts commit 94ecd84c8f24d210f537c495e044ded266f1147b. --- .../Input/LibInput/LibInputBackend.cs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 6e26794b24..15d42789d4 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,15 +1,16 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { public class LibInputBackend : IInputBackend { - private const string LibInput = nameof(Logging.LogArea.X11Platform) + "/" + nameof(LibInput); private IScreenInfoProvider _screen; private IInputRoot _inputRoot; private readonly Queue _inputThreadActions = new Queue(); @@ -28,21 +29,15 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput new Thread(()=>InputThread(ctx)).Start(); } + + private unsafe void InputThread(IntPtr ctx) { var fd = libinput_get_fd(ctx); var timeval = stackalloc IntPtr[2]; - if (!Directory.Exists("/dev/input")) - { - if (Logging.Logger.IsEnabled(Logging.LogEventLevel.Warning,LibInput)) - { - Logging.Logger.TryGet(Logging.LogEventLevel.Warning, LibInput) - ?.Log(this, "Not connect any input device."); - } - return; - } + foreach (var f in Directory.GetFiles("/dev/input", "event*")) libinput_path_add_device(ctx, f); while (true) From 197f514ae7fbb94849ce048c28434de4f1f2aee0 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 10:47:11 +0200 Subject: [PATCH 015/453] feat(X11): NullInputBackend --- .../Input/LibInput/LibInputBackend.cs | 6 ------ .../Input/NullInput/NullInputBackend.cs | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 15d42789d4..702ae3f8e5 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; -using Avalonia.Threading; using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { @@ -29,8 +27,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput new Thread(()=>InputThread(ctx)).Start(); } - - private unsafe void InputThread(IntPtr ctx) { var fd = libinput_get_fd(ctx); @@ -143,8 +139,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput } } - - public void Initialize(IScreenInfoProvider screen, Action onInput) { diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs new file mode 100644 index 0000000000..551c0995a2 --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs @@ -0,0 +1,17 @@ +using System; +using Avalonia.Input; +using Avalonia.Input.Raw; + +namespace Avalonia.LinuxFramebuffer.Input.NullInput; + +internal class NullInputBackend : IInputBackend +{ + + public void Initialize(IScreenInfoProvider screen, Action onInput) + { + } + + public void SetInputRoot(IInputRoot root) + { + } +} From 46185b6a6eb78a887567dc483c5a1351fab61e30 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 2 Aug 2022 10:59:33 +0200 Subject: [PATCH 016/453] feat: add overload thath accept IInputBackend to Linux AppBuilder --- .../LinuxFramebufferPlatform.cs | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index a642766809..c819407cc9 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -15,7 +15,6 @@ using Avalonia.LinuxFramebuffer.Output; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Threading; using JetBrains.Annotations; namespace Avalonia.LinuxFramebuffer @@ -37,9 +36,9 @@ namespace Avalonia.LinuxFramebuffer Threading = new InternalPlatformThreadingInterface(); if (_fb is IGlOutputBackend gl) AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface); - + var opts = AvaloniaLocator.Current.GetService() ?? new LinuxFramebufferPlatformOptions(); - + AvaloniaLocator.CurrentMutable .Bind().ToConstant(Threading) .Bind().ToConstant(new DefaultRenderTimer(opts.Fps)) @@ -50,12 +49,12 @@ namespace Avalonia.LinuxFramebuffer .Bind().ToSingleton(); } - - internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend) where T : AppBuilderBase, new() + + internal static LinuxFramebufferLifetime Initialize(T builder, IOutputBackend outputBackend, IInputBackend inputBackend) where T : AppBuilderBase, new() { var platform = new LinuxFramebufferPlatform(outputBackend); builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev"); - return new LinuxFramebufferLifetime(platform._fb); + return new LinuxFramebufferLifetime(platform._fb, inputBackend); } } @@ -71,13 +70,13 @@ namespace Avalonia.LinuxFramebuffer { _fb = fb; } - + public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend input) { _fb = fb; _inputBackend = input; } - + public Control MainView { get => (Control)_topLevel?.Content; @@ -117,7 +116,7 @@ namespace Avalonia.LinuxFramebuffer { Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args)); } - + public void Shutdown(int exitCode) { ExitCode = exitCode; @@ -131,22 +130,22 @@ namespace Avalonia.LinuxFramebuffer public static class LinuxFramebufferPlatformExtensions { - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1) + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev = null, double scaling = 1, IInputBackend inputBackend = default) where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }); - public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling) + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend); + public static int StartLinuxFbDev(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend inputBackend = default) where T : AppBuilderBase, new() => - StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }); - - public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling}); - public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null) - where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options)); - - public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend backend) + StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend); + + public static int StartLinuxDrm(this T builder, string[] args, string card = null, double scaling = 1, IInputBackend inputBackend = default) + where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend); + public static int StartLinuxDrm(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null, IInputBackend inputBackend = default) + where T : AppBuilderBase, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend); + + public static int StartLinuxDirect(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend inputBackend = default) where T : AppBuilderBase, new() { - var lifetime = LinuxFramebufferPlatform.Initialize(builder, backend); + var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); builder.SetupWithLifetime(lifetime); lifetime.Start(args); builder.Instance.Run(lifetime.Token); From 64c94ca3415e3c5f9961c62eb6ca090ee7ab5c0a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 3 Aug 2022 09:24:17 +0200 Subject: [PATCH 017/453] fix: NullInputBackend visibility --- .../Input/NullInput/NullInputBackend.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs index 551c0995a2..05dd0195b1 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/NullInput/NullInputBackend.cs @@ -4,9 +4,8 @@ using Avalonia.Input.Raw; namespace Avalonia.LinuxFramebuffer.Input.NullInput; -internal class NullInputBackend : IInputBackend +public class NullInputBackend : IInputBackend { - public void Initialize(IScreenInfoProvider screen, Action onInput) { } From 3717aec4f5f4e9e5c88d5b03b22d067fdd67a568 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 3 Aug 2022 14:32:38 +0300 Subject: [PATCH 018/453] Use GetFeature + API lease approach for accessing SKCanvas --- samples/RenderDemo/Pages/CustomSkiaPage.cs | 6 +- src/Avalonia.Base/Media/DrawingGroup.cs | 2 + .../Platform/IDrawingContextImpl.cs | 14 +++ .../Drawing/CompositionDrawingContext.cs | 2 + .../Composition/Server/DrawingContextProxy.cs | 3 + .../SceneGraph/DeferredDrawingContextImpl.cs | 2 + .../HeadlessPlatformRenderInterface.cs | 5 + src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 97 +++++++++++++++++-- .../Helpers/DrawingContextHelper.cs | 12 ++- .../Avalonia.Skia/ISkiaDrawingContextImpl.cs | 15 --- .../ISkiaSharpApiLeaseFeature.cs | 20 ++++ .../Media/DrawingContextImpl.cs | 1 + .../NullDrawingContextImpl.cs | 5 +- 13 files changed, 152 insertions(+), 32 deletions(-) delete mode 100644 src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs create mode 100644 src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs diff --git a/samples/RenderDemo/Pages/CustomSkiaPage.cs b/samples/RenderDemo/Pages/CustomSkiaPage.cs index 9c524a7932..bf27747154 100644 --- a/samples/RenderDemo/Pages/CustomSkiaPage.cs +++ b/samples/RenderDemo/Pages/CustomSkiaPage.cs @@ -40,14 +40,16 @@ namespace RenderDemo.Pages static Stopwatch St = Stopwatch.StartNew(); public void Render(IDrawingContextImpl context) { - var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas; - if (canvas == null) + var leaseFeature = context.GetFeature(); + if (leaseFeature == null) using (var c = new DrawingContext(context, false)) { c.DrawText(_noSkia, new Point()); } else { + using var lease = leaseFeature.Lease(); + var canvas = lease.SkCanvas; canvas.Save(); // create the first shader var colors = new SKColor[] { diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 603bb1c1c1..e71f568207 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -228,6 +228,8 @@ namespace Avalonia.Media throw new NotImplementedException(); } + public object? GetFeature(Type t) => null; + public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) { throw new NotImplementedException(); diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index d84a509234..6aa5eeea3d 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -172,6 +172,20 @@ namespace Avalonia.Platform /// /// Custom draw operation void Custom(ICustomDrawOperation custom); + + /// + /// Attempts to get an optional feature from the drawing context implementation + /// + object? GetFeature(Type t); + } + + public static class DrawingContextImplExtensions + { + /// + /// Attempts to get an optional feature from the drawing context implementation + /// + public static T? GetFeature(this IDrawingContextImpl context) where T : class => + (T?)context.GetFeature(typeof(T)); } public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index 01216d19ed..30b57883fc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -156,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW ++_drawOperationIndex; } + public object? GetFeature(Type t) => null; + /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index 7eb35a68ed..03859d241f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using Avalonia.Media; using Avalonia.Media.Imaging; @@ -155,6 +156,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.Custom(custom); } + public object? GetFeature(Type t) => _impl.GetFeature(t); + public class VisualBrushRenderer : IVisualBrushRenderer { public CompositionDrawList? VisualBrushDrawList { get; set; } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 07082e4ac3..d6766fa9b8 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -203,6 +203,8 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } + public object? GetFeature(Type t) => null; + /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 5576368240..cb23c6c336 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -416,6 +416,11 @@ namespace Avalonia.Headless } + public object GetFeature(Type t) + { + return null; + } + public void DrawLine(IPen pen, Point p1, Point p2) { } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 8293769138..99ab60d1ac 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -10,6 +10,7 @@ using Avalonia.Rendering.SceneGraph; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; using Avalonia.Media.Imaging; +using JetBrains.Annotations; using SkiaSharp; namespace Avalonia.Skia @@ -17,7 +18,7 @@ namespace Avalonia.Skia /// /// Skia based drawing context. /// - internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport + internal class DrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport { private IDisposable[] _disposables; private readonly Vector _dpi; @@ -38,7 +39,8 @@ namespace Avalonia.Skia private readonly SKPaint _fillPaint = new SKPaint(); private readonly SKPaint _boxShadowPaint = new SKPaint(); private static SKShader s_acrylicNoiseShader; - private readonly ISkiaGpuRenderSession _session; + private readonly ISkiaGpuRenderSession _session; + private bool _leased = false; /// /// Context create info. @@ -83,6 +85,47 @@ namespace Avalonia.Skia public ISkiaGpuRenderSession CurrentSession; } + class SkiaLeaseFeature : ISkiaSharpApiLeaseFeature + { + private readonly DrawingContextImpl _context; + + public SkiaLeaseFeature(DrawingContextImpl context) + { + _context = context; + } + + public ISkiaSharpApiLease Lease() + { + _context.CheckLease(); + return new ApiLease(_context); + } + + class ApiLease : ISkiaSharpApiLease + { + private DrawingContextImpl _context; + private readonly SKMatrix _revertTransform; + + public ApiLease(DrawingContextImpl context) + { + _revertTransform = context.Canvas.TotalMatrix; + _context = context; + _context._leased = true; + } + + public SKCanvas SkCanvas => _context.Canvas; + public GRContext GrContext => _context.GrContext; + public SKSurface SkSurface => _context.Surface; + public double CurrentOpacity => _context._currentOpacity; + + public void Dispose() + { + _context.Canvas.SetMatrix(_revertTransform); + _context._leased = false; + _context = null; + } + } + } + /// /// Create new drawing context. /// @@ -123,20 +166,23 @@ namespace Avalonia.Skia public SKCanvas Canvas { get; } public SKSurface Surface { get; } - SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas; - SKSurface ISkiaDrawingContextImpl.SkSurface => Surface; - GRContext ISkiaDrawingContextImpl.GrContext => _grContext; - double ISkiaDrawingContextImpl.CurrentOpacity => _currentOpacity; - + private void CheckLease() + { + if (_leased) + throw new InvalidOperationException("The underlying graphics API is currently leased"); + } + /// public void Clear(Color color) { + CheckLease(); Canvas.Clear(color.ToSKColor()); } /// public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { + CheckLease(); var drawableImage = (IDrawableBitmapImpl)source.Item; var s = sourceRect.ToSKRect(); var d = destRect.ToSKRect(); @@ -157,6 +203,7 @@ namespace Avalonia.Skia /// public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect) { + CheckLease(); PushOpacityMask(opacityMask, opacityMaskRect); DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default); PopOpacityMask(); @@ -165,6 +212,7 @@ namespace Avalonia.Skia /// public void DrawLine(IPen pen, Point p1, Point p2) { + CheckLease(); using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) { if (paint.Paint is object) @@ -177,6 +225,7 @@ namespace Avalonia.Skia /// public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { + CheckLease(); var impl = (GeometryImpl) geometry; var size = geometry.Bounds.Size; @@ -260,6 +309,7 @@ namespace Avalonia.Skia { if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0) return; + CheckLease(); var rc = rect.Rect.ToSKRect(); var isRounded = rect.IsRounded; @@ -296,6 +346,7 @@ namespace Avalonia.Skia { if (rect.Rect.Height <= 0 || rect.Rect.Width <= 0) return; + CheckLease(); // Arbitrary chosen values // On OSX Skia breaks OpenGL context when asked to draw, e. g. (0, 0, 623, 6666600) rect if (rect.Rect.Height > 8192 || rect.Rect.Width > 8192) @@ -421,7 +472,8 @@ namespace Avalonia.Skia { if (rect.Height <= 0 || rect.Width <= 0) return; - + CheckLease(); + var rc = rect.ToSKRect(); if (brush != null) @@ -447,6 +499,7 @@ namespace Avalonia.Skia /// public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { + CheckLease(); using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; @@ -459,18 +512,21 @@ namespace Avalonia.Skia /// public IDrawingContextLayerImpl CreateLayer(Size size) { + CheckLease(); return CreateRenderTarget(size, true); } /// public void PushClip(Rect clip) { + CheckLease(); Canvas.Save(); Canvas.ClipRect(clip.ToSKRect()); } public void PushClip(RoundedRect clip) { + CheckLease(); Canvas.Save(); Canvas.ClipRoundRect(clip.ToSKRoundRect(), antialias:true); } @@ -478,12 +534,14 @@ namespace Avalonia.Skia /// public void PopClip() { + CheckLease(); Canvas.Restore(); } /// public void PushOpacity(double opacity) { + CheckLease(); _opacityStack.Push(_currentOpacity); _currentOpacity *= opacity; } @@ -491,6 +549,7 @@ namespace Avalonia.Skia /// public void PopOpacity() { + CheckLease(); _currentOpacity = _opacityStack.Pop(); } @@ -499,6 +558,7 @@ namespace Avalonia.Skia { if(_disposed) return; + CheckLease(); try { if (_grContext != null) @@ -523,6 +583,7 @@ namespace Avalonia.Skia /// public void PushGeometryClip(IGeometryImpl clip) { + CheckLease(); Canvas.Save(); Canvas.ClipPath(((GeometryImpl)clip).EffectivePath, SKClipOperation.Intersect, true); } @@ -530,12 +591,14 @@ namespace Avalonia.Skia /// public void PopGeometryClip() { + CheckLease(); Canvas.Restore(); } /// public void PushBitmapBlendMode(BitmapBlendingMode blendingMode) { + CheckLease(); _blendingModeStack.Push(_currentBlendingMode); _currentBlendingMode = blendingMode; } @@ -543,14 +606,20 @@ namespace Avalonia.Skia /// public void PopBitmapBlendMode() { + CheckLease(); _currentBlendingMode = _blendingModeStack.Pop(); } - public void Custom(ICustomDrawOperation custom) => custom.Render(this); + public void Custom(ICustomDrawOperation custom) + { + CheckLease(); + custom.Render(this); + } /// public void PushOpacityMask(IBrush mask, Rect bounds) { + CheckLease(); // TODO: This should be disposed var paint = new SKPaint(); @@ -561,6 +630,7 @@ namespace Avalonia.Skia /// public void PopOpacityMask() { + CheckLease(); using (var paint = new SKPaint { BlendMode = SKBlendMode.DstIn }) { Canvas.SaveLayer(paint); @@ -580,6 +650,7 @@ namespace Avalonia.Skia get { return _currentTransform; } set { + CheckLease(); if (_currentTransform == value) return; @@ -596,6 +667,14 @@ namespace Avalonia.Skia } } + [CanBeNull] + public object GetFeature(Type t) + { + if (t == typeof(ISkiaSharpApiLeaseFeature)) + return new SkiaLeaseFeature(this); + return null; + } + /// /// Configure paint wrapper for using gradient brush. /// diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs index d0b45b7c5d..a078c364a2 100644 --- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs @@ -33,7 +33,7 @@ namespace Avalonia.Skia.Helpers /// Unsupported - Wraps a GPU Backed SkiaSurface in an Avalonia DrawingContext. /// [Obsolete] - public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables) + public static IDrawingContextImpl WrapSkiaSurface(this SKSurface surface, GRContext grContext, Vector dpi, params IDisposable[] disposables) { var createInfo = new DrawingContextImpl.CreateInfo { @@ -50,7 +50,7 @@ namespace Avalonia.Skia.Helpers /// Unsupported - Wraps a non-GPU Backed SkiaSurface in an Avalonia DrawingContext. /// [Obsolete] - public static ISkiaDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables) + public static IDrawingContextImpl WrapSkiaSurface(this SKSurface surface, Vector dpi, params IDisposable[] disposables) { var createInfo = new DrawingContextImpl.CreateInfo { @@ -63,7 +63,7 @@ namespace Avalonia.Skia.Helpers } [Obsolete] - public static ISkiaDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null) + public static IDrawingContextImpl CreateDrawingContext(Size size, Vector dpi, GRContext grContext = null) { if (grContext is null) { @@ -90,9 +90,11 @@ namespace Avalonia.Skia.Helpers } [Obsolete] - public static void DrawTo(this ISkiaDrawingContextImpl source, ISkiaDrawingContextImpl destination, SKPaint paint = null) + public static void DrawTo(this IDrawingContextImpl source, IDrawingContextImpl destination, SKPaint paint = null) { - destination.SkCanvas.DrawSurface(source.SkSurface, new SKPoint(0, 0), paint); + var src = (DrawingContextImpl)source; + var dst = (DrawingContextImpl)destination; + dst.Canvas.DrawSurface(src.Surface, new SKPoint(0, 0), paint); } } } diff --git a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs deleted file mode 100644 index 1b60154d46..0000000000 --- a/src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Avalonia.Metadata; -using Avalonia.Platform; -using SkiaSharp; - -namespace Avalonia.Skia -{ - [Unstable] - public interface ISkiaDrawingContextImpl : IDrawingContextImpl - { - SKCanvas SkCanvas { get; } - GRContext GrContext { get; } - SKSurface SkSurface { get; } - double CurrentOpacity { get; } - } -} diff --git a/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs b/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs new file mode 100644 index 0000000000..b3966c0324 --- /dev/null +++ b/src/Skia/Avalonia.Skia/ISkiaSharpApiLeaseFeature.cs @@ -0,0 +1,20 @@ +using System; +using Avalonia.Metadata; +using SkiaSharp; + +namespace Avalonia.Skia; + +[Unstable] +public interface ISkiaSharpApiLeaseFeature +{ + public ISkiaSharpApiLease Lease(); +} + +[Unstable] +public interface ISkiaSharpApiLease : IDisposable +{ + SKCanvas SkCanvas { get; } + GRContext GrContext { get; } + SKSurface SkSurface { get; } + double CurrentOpacity { get; } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index a7f1d9c3e5..180ae491b3 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -614,5 +614,6 @@ namespace Avalonia.Direct2D1.Media } public void Custom(ICustomDrawOperation custom) => custom.Render(this); + public object GetFeature(Type t) => null; } } diff --git a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs index 6fb2a5ac24..8436881122 100644 --- a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs +++ b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs @@ -1,4 +1,5 @@ -using Avalonia.Media; +using System; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Utilities; @@ -99,5 +100,7 @@ namespace Avalonia.Benchmarks public void Custom(ICustomDrawOperation custom) { } + + public object GetFeature(Type t) => null; } } From 75870f751eef9beb95ea81a4cf3b31faf7fd6159 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 3 Aug 2022 15:30:01 +0200 Subject: [PATCH 019/453] Rendering part of PreeditText --- samples/Sandbox/MainWindow.axaml | 5 ++ samples/Sandbox/MainWindow.axaml.cs | 1 + .../Presenters/TextPresenter.cs | 57 ++++++++++++++++++- .../TextBoxTextInputMethodClient.cs | 25 ++++++-- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 6929f192c7..20d7a53a11 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,4 +1,9 @@ + + + + + diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 3d54036d29..23d45edf6a 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.Presenters; using Avalonia.Markup.Xaml; using Avalonia.Win32.WinRT.Composition; diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index e540f58195..3f4d881735 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -54,6 +54,15 @@ namespace Avalonia.Controls.Presenters o => o.Text, (o, v) => o.Text = v); + /// + /// Defines the property. + /// + public static readonly DirectProperty PreeditTextProperty = + AvaloniaProperty.RegisterDirect( + nameof(PreeditText), + o => o.PreeditText, + (o, v) => o.PreeditText = v); + /// /// Defines the property. /// @@ -90,6 +99,7 @@ namespace Avalonia.Controls.Presenters private CharacterHit _lastCharacterHit; private Rect _caretBounds; private Point _navigationPosition; + private string? _preeditText; static TextPresenter() { @@ -124,6 +134,12 @@ namespace Avalonia.Controls.Presenters set => SetAndRaise(TextProperty, ref _text, value); } + public string? PreeditText + { + get => _preeditText; + set => SetAndRaise(PreeditTextProperty, ref _preeditText, value); + } + /// /// Gets or sets the font family. /// @@ -479,6 +495,18 @@ namespace Avalonia.Controls.Presenters } } + private string? GetText() + { + if (!string.IsNullOrEmpty(_preeditText)) + { + var text = _text?.Substring(0, _caretIndex) + _preeditText + _text?.Substring(_caretIndex); + + return text; + } + + return _text; + } + /// /// Creates the used to render the text. /// @@ -487,7 +515,7 @@ namespace Avalonia.Controls.Presenters { TextLayout result; - var text = Text; + var text = GetText(); var typeface = new Typeface(FontFamily, FontStyle, FontWeight); @@ -496,11 +524,11 @@ namespace Avalonia.Controls.Presenters var start = Math.Min(selectionStart, selectionEnd); var length = Math.Max(selectionStart, selectionEnd) - start; - IReadOnlyList>? textStyleOverrides = null; + List>? textStyleOverrides = null; if (length > 0 && SelectionForegroundBrush != null) { - textStyleOverrides = new[] + textStyleOverrides = new List> { new ValueSpan(start, length, new GenericTextRunProperties(typeface, FontSize, @@ -508,6 +536,28 @@ namespace Avalonia.Controls.Presenters }; } + var foreground = Foreground; + + if (!string.IsNullOrEmpty(_preeditText)) + { + var preeditHighlight = new ValueSpan(_caretIndex, _preeditText.Length, + new GenericTextRunProperties(typeface, FontSize, + foregroundBrush: foreground, + textDecorations: TextDecorations.Underline)); + + if (textStyleOverrides == null) + { + textStyleOverrides = new List> + { + preeditHighlight + }; + } + else + { + textStyleOverrides.Add(preeditHighlight); + } + } + if (PasswordChar != default(char) && !RevealPassword) { result = CreateTextLayoutInternal(_constraint, new string(PasswordChar, text?.Length ?? 0), typeface, @@ -814,6 +864,7 @@ namespace Avalonia.Controls.Presenters case nameof (FontStretch): case nameof (Text): + case nameof (PreeditText): case nameof (TextAlignment): case nameof (TextWrapping): diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index 4d2ed03440..f9eb90611d 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls { internal class TextBoxTextInputMethodClient : ITextInputMethodClient { - private InputElement? _parent; + private TextBox? _parent; private TextPresenter? _presenter; public Rect CursorRectangle @@ -36,19 +36,29 @@ namespace Avalonia.Controls public event EventHandler? CursorRectangleChanged; public IVisual TextViewVisual => _presenter!; public event EventHandler? TextViewVisualChanged; - public bool SupportsPreedit => false; - public void SetPreeditText(string text) => throw new NotSupportedException(); + public bool SupportsPreedit => true; + public void SetPreeditText(string? text) + { + if(_presenter == null) + { + return; + } + + _presenter.PreeditText = text; + } public bool SupportsSurroundingText => false; - public TextInputMethodSurroundingText SurroundingText => throw new NotSupportedException(); + + public event EventHandler? SurroundingTextChanged { add { } remove { } } + public TextInputMethodSurroundingText SurroundingText => throw new NotSupportedException(); public string? TextBeforeCursor => null; public string? TextAfterCursor => null; private void OnCaretBoundsChanged(object? sender, EventArgs e) => CursorRectangleChanged?.Invoke(this, EventArgs.Empty); - public void SetPresenter(TextPresenter? presenter, InputElement? parent) + public void SetPresenter(TextPresenter? presenter, TextBox? parent) { _parent = parent; @@ -63,6 +73,11 @@ namespace Avalonia.Controls { _presenter.CaretBoundsChanged += OnCaretBoundsChanged; } + + if(presenter == null) + { + SetPreeditText(null); + } TextViewVisualChanged?.Invoke(this, EventArgs.Empty); CursorRectangleChanged?.Invoke(this, EventArgs.Empty); From 3d327bc046ab6295fac0634b9da06fec92a8d493 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 3 Aug 2022 18:50:23 +0300 Subject: [PATCH 020/453] Fix and optimize StringFormatConverter. StringFormatConverter should validate values and return AvaloniaProperty.UnsetValue in case of incorrect value or exception. --- .../Converters/StringFormatConverter.cs | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Converters/StringFormatConverter.cs b/src/Avalonia.Controls/Converters/StringFormatConverter.cs index ae920dac7e..7075d3de99 100644 --- a/src/Avalonia.Controls/Converters/StringFormatConverter.cs +++ b/src/Avalonia.Controls/Converters/StringFormatConverter.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using Avalonia.Data; using Avalonia.Data.Converters; namespace Avalonia.Controls.Converters; @@ -15,13 +13,25 @@ public class StringFormatConverter : IMultiValueConverter { public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { - try - { - return string.Format((string)values[0]!, values.Skip(1).ToArray()); - } - catch (Exception e) + if (values != null && + values.Count == 5 && + values[0] is string format && + values[1] is double && + values[2] is double && + values[3] is double && + values[4] is double) + { - return new BindingNotification(e, BindingErrorType.Error); + + try + { + return string.Format(format, values[1], values[2], values[3], values[4]); + } + catch + { + return AvaloniaProperty.UnsetValue; + } } + return AvaloniaProperty.UnsetValue; } } From e7797eea673ecac0da6463314c43ce5645a96064 Mon Sep 17 00:00:00 2001 From: ErrorCraft <51973682+ErrorCraft@users.noreply.github.com> Date: Wed, 3 Aug 2022 20:51:10 +0200 Subject: [PATCH 021/453] Add Sector shape --- src/Avalonia.Base/Utilities/MathUtilities.cs | 35 +++++++++++ src/Avalonia.Controls/Shapes/Sector.cs | 65 ++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/Avalonia.Controls/Shapes/Sector.cs diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs index d381979c1e..3c48c3469e 100644 --- a/src/Avalonia.Base/Utilities/MathUtilities.cs +++ b/src/Avalonia.Base/Utilities/MathUtilities.cs @@ -324,6 +324,41 @@ namespace Avalonia.Utilities return angle * 2 * Math.PI; } + /// + /// Calculates the point of an angle on an ellipse. + /// + /// The centre point of the ellipse. + /// The x radius of the ellipse. + /// The y radius of the ellipse. + /// The angle in radians. + /// A point on the ellipse. + public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle) + { + return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y); + } + + /// + /// Gets the minimum and maximum from the specified numbers. + /// + /// The first number. + /// The second number. + /// A tuple containing the minimum and maximum of the two specified numbers. + public static (double min, double max) GetMinMax(double a, double b) + { + return a < b ? (a, b) : (b, a); + } + + /// + /// Gets the minimum and maximum from the specified number and the difference with that number. + /// + /// The initial value to use. + /// The difference for . + /// A tuple containing the minimum and maximum of the specified number and the difference with that number. + public static (double min, double max) GetMinMaxFromDelta(double initialValue, double delta) + { + return GetMinMax(initialValue, initialValue + delta); + } + private static void ThrowCannotBeGreaterThanException(T min, T max) { throw new ArgumentException($"{min} cannot be greater than {max}."); diff --git a/src/Avalonia.Controls/Shapes/Sector.cs b/src/Avalonia.Controls/Shapes/Sector.cs new file mode 100644 index 0000000000..5d2f6701a7 --- /dev/null +++ b/src/Avalonia.Controls/Shapes/Sector.cs @@ -0,0 +1,65 @@ +using System; +using Avalonia.Media; +using Avalonia.Utilities; + +namespace Avalonia.Controls.Shapes +{ + public class Sector : Shape + { + public static readonly StyledProperty StartAngleProperty = AvaloniaProperty.Register(nameof(StartAngle), 0.0d); + public static readonly StyledProperty AngleProperty = AvaloniaProperty.Register(nameof(Angle), 0.0d); + + public double StartAngle + { + get => GetValue(StartAngleProperty); + set => SetValue(StartAngleProperty, value); + } + + public double Angle + { + get => GetValue(AngleProperty); + set => SetValue(AngleProperty, value); + } + + static Sector() + { + StrokeThicknessProperty.OverrideDefaultValue(1.0d); + AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, AngleProperty); + } + + protected override Geometry? CreateDefiningGeometry() + { + Rect rect = new Rect(Bounds.Size); + Rect deflatedRect = rect.Deflate(StrokeThickness * 0.5d); + + if (Angle >= 360.0d || Angle <= -360.0d) + { + return new EllipseGeometry(deflatedRect); + } + + if (Angle == 0.0d) + { + return new StreamGeometry(); + } + + (double startAngle, double endAngle) = MathUtilities.GetMinMaxFromDelta(MathUtilities.Deg2Rad(StartAngle), MathUtilities.Deg2Rad(Angle)); + + Point centre = new Point(rect.Width * 0.5d, rect.Height * 0.5d); + double radiusX = deflatedRect.Width * 0.5d; + double radiusY = deflatedRect.Height * 0.5d; + Point startCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, startAngle); + Point endCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, endAngle); + Size size = new Size(radiusX, radiusY); + + StreamGeometry streamGeometry = new StreamGeometry(); + using StreamGeometryContext streamGeometryContext = streamGeometry.Open(); + + streamGeometryContext.BeginFigure(startCurvePoint, false); + streamGeometryContext.ArcTo(endCurvePoint, size, 0.0d, Math.Abs(Angle) > 180.0d, SweepDirection.Clockwise); + streamGeometryContext.LineTo(centre); + streamGeometryContext.EndFigure(true); + + return streamGeometry; + } + } +} From e8719e018db82a6998900e61bea3f11470eade9f Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 4 Aug 2022 14:24:53 +0200 Subject: [PATCH 022/453] Fix GlyphRun.GetTralingWhitespaceLengthRightToLeft --- src/Avalonia.Base/Media/GlyphRun.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index cae7a8fe75..f207b3c636 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -786,14 +786,15 @@ namespace Avalonia.Media var clusterLength = 1; - while (i - 1 >= 0) + var j = i; + + while (j - 1 >= 0) { - var nextCluster = GlyphClusters[i - 1]; + var nextCluster = GlyphClusters[--j]; if (currentCluster == nextCluster) { - clusterLength++; - i--; + clusterLength++; continue; } @@ -808,7 +809,7 @@ namespace Avalonia.Media trailingWhitespaceLength += clusterLength; - glyphCount++; + glyphCount += clusterLength; } } From 0856cfaff985c15614b3bd647cf827b021a0444a Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 4 Aug 2022 14:25:12 +0200 Subject: [PATCH 023/453] Remove redundant comment --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index fa1ab6fd29..7495956cd2 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -266,10 +266,6 @@ namespace Avalonia.Media.TextFormatting { offset = Math.Max(0, currentPosition - shapedRun.Text.Start); } - //else - //{ - // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length); - //} characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength); From 9acb2a11ee4793568b01769d2ffc9160d8ef004c Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 2 Aug 2022 18:42:02 +0200 Subject: [PATCH 024/453] Custom setters implement Equals/GetHashCode and have optimized code gen --- ...aloniaXamlIlDeferredResourceTransformer.cs | 50 +++++- .../AvaloniaXamlIlSetterTransformer.cs | 26 ++- .../XamlIlAvaloniaPropertyHelper.cs | 152 ++++++++++++++---- .../Avalonia.Markup.Xaml.Loader/xamlil.github | 2 +- 4 files changed, 182 insertions(+), 48 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs index 662263e513..c29dd94886 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDeferredResourceTransformer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using XamlX.Ast; using XamlX.Emit; @@ -47,7 +48,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers return !node.Type.GetClrType().IsValueType; } - class AdderSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + class AdderSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { private readonly IXamlMethod _getter; private readonly IXamlMethod _adder; @@ -58,16 +59,22 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers _adder = adder; TargetType = getter.DeclaringType; Parameters = adder.ParametersWithThis().Skip(1).ToList(); + + bool allowNull = Parameters.Last().AcceptsNull(); + BinderParameters = new PropertySetterBinderParameters + { + AllowMultiple = true, + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters - { - AllowMultiple = true - }; + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; } + public void Emit(IXamlILEmitter emitter) { var locals = new Stack(); @@ -80,11 +87,40 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } emitter.EmitCall(_getter); - while (locals.Count > 0) + while (locals.Count>0) using (var loc = locals.Pop()) emitter.Ldloc(loc.Local); emitter.EmitCall(_adder, true); } + + public void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.EmitCall(_getter); + + for (var i = 0; i < arguments.Count; ++i) + context.Emit(arguments[i], emitter, Parameters[i]); + + emitter.EmitCall(_adder, true); + } + + public bool Equals(AdderSetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return _getter.Equals(other._getter) && _adder.Equals(other._adder); + } + + public override bool Equals(object obj) + => Equals(obj as AdderSetter); + + public override int GetHashCode() + => (_getter.GetHashCode() * 397) ^ _adder.GetHashCode(); } } } 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..ceaec972f6 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs @@ -75,17 +75,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { Getter = setterType.Methods.First(m => m.Name == "get_Value"); var method = setterType.Methods.First(m => m.Name == "set_Value"); - Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding)); - Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType)); - Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, types.IBinding, false)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, types.UnsetValueType, false)); + Setters.Add(new XamlIlDirectCallPropertySetter(method, targetType, targetType.AcceptsNull())); } - class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter + sealed class XamlIlDirectCallPropertySetter : IXamlPropertySetter, IXamlEmitablePropertySetter { private readonly IXamlMethod _method; private readonly IXamlType _type; public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters(); + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; } public void Emit(IXamlILEmitter codegen) { @@ -94,13 +94,27 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers codegen.EmitCall(_method, true); } - public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type) + public XamlIlDirectCallPropertySetter(IXamlMethod method, IXamlType type, bool allowNull) { _method = method; _type = type; Parameters = new[] {type}; TargetType = method.ThisOrFirstParameter(); + BinderParameters = new PropertySetterBinderParameters + { + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } + + private bool Equals(XamlIlDirectCallPropertySetter other) + => Equals(_method, other._method) && Equals(_type, other._type); + + public override bool Equals(object obj) + => Equals(obj as XamlIlDirectCallPropertySetter); + + public override int GetHashCode() + => (_method.GetHashCode() * 397) ^ _type.GetHashCode(); } } } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs index 5c7a80e680..6c9d510ba0 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlAvaloniaPropertyHelper.cs @@ -206,38 +206,64 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions Setters.Insert(0, new UnsetValueSetter(types, original.DeclaringType, field)); } - abstract class AvaloniaPropertyCustomSetter : IXamlPropertySetter, IXamlEmitablePropertySetter + abstract class AvaloniaPropertyCustomSetter : IXamlILOptimizedEmitablePropertySetter, IEquatable { - protected AvaloniaXamlIlWellKnownTypes Types; - protected IXamlField AvaloniaProperty; + protected readonly AvaloniaXamlIlWellKnownTypes Types; + protected readonly IXamlField AvaloniaProperty; - public AvaloniaPropertyCustomSetter(AvaloniaXamlIlWellKnownTypes types, + protected AvaloniaPropertyCustomSetter( + AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) + IXamlField avaloniaProperty, + bool allowNull) { Types = types; AvaloniaProperty = avaloniaProperty; TargetType = declaringType; + BinderParameters = new PropertySetterBinderParameters + { + AllowXNull = allowNull, + AllowRuntimeNull = allowNull + }; } public IXamlType TargetType { get; } - public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters - { - AllowXNull = false - }; + public PropertySetterBinderParameters BinderParameters { get; } public IReadOnlyList Parameters { get; set; } - public abstract void Emit(IXamlILEmitter codegen); + + public abstract void Emit(IXamlILEmitter emitter); + + public abstract void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments); + + public bool Equals(AvaloniaPropertyCustomSetter other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return GetType() == other.GetType() && AvaloniaProperty.Equals(other.AvaloniaProperty); + } + + public override bool Equals(object obj) + => Equals(obj as AvaloniaPropertyCustomSetter); + + public override int GetHashCode() + => AvaloniaProperty.GetHashCode(); } class BindingSetter : AvaloniaPropertyCustomSetter { public BindingSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty) + IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty, false) { - Parameters = new[] {types.IBinding}; + Parameters = new[] { types.IBinding }; } public override void Emit(IXamlILEmitter emitter) @@ -246,10 +272,25 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions emitter .Stloc(bloc.Local) .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local) - // TODO: provide anchor? - .Ldnull(); - emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); + .Ldloc(bloc.Local); + EmitAnchorAndBind(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[0], emitter, Parameters[0]); + EmitAnchorAndBind(emitter); + } + + private void EmitAnchorAndBind(IXamlILEmitter emitter) + { + emitter + .Ldnull() // TODO: provide anchor? + .EmitCall(Types.AvaloniaObjectBindMethod, true); } } @@ -257,7 +298,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public BindingWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, - IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty) + IXamlField avaloniaProperty) : base(types, declaringType, avaloniaProperty, false) { Parameters = new[] { types.BindingPriority, types.IBinding }; } @@ -265,15 +306,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions public override void Emit(IXamlILEmitter emitter) { using (var bloc = emitter.LocalsPool.GetLocal(Types.IBinding)) - using (var priorityLocal = emitter.LocalsPool.GetLocal(Types.Int)) emitter .Stloc(bloc.Local) - .Stloc(priorityLocal.Local) + .Pop() // ignore priority .Ldsfld(AvaloniaProperty) - .Ldloc(bloc.Local) - // TODO: provide anchor? - .Ldnull(); - emitter.EmitCall(Types.AvaloniaObjectBindMethod, true); + .Ldloc(bloc.Local); + EmitAnchorAndBind(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[1], emitter, Parameters[1]); + EmitAnchorAndBind(emitter); + } + + private void EmitAnchorAndBind(IXamlILEmitter emitter) + { + emitter + .Ldnull() // TODO: provide anchor? + .EmitCall(Types.AvaloniaObjectBindMethod, true); } } @@ -281,7 +336,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { public SetValueWithPrioritySetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty, IXamlType propertyType) - : base(types, declaringType, avaloniaProperty) + : base(types, declaringType, avaloniaProperty, propertyType.AcceptsNull()) { Parameters = new[] { types.BindingPriority, propertyType }; } @@ -295,9 +350,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions - value */ - var method = Types.AvaloniaObjectSetStyledPropertyValue - .MakeGenericMethod(new[] { Parameters[1] }); - using (var valueLocal = emitter.LocalsPool.GetLocal(Parameters[1])) using (var priorityLocal = emitter.LocalsPool.GetLocal(Types.Int)) emitter @@ -305,25 +357,57 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions .Stloc(priorityLocal.Local) .Ldsfld(AvaloniaProperty) .Ldloc(valueLocal.Local) - .Ldloc(priorityLocal.Local) - .EmitCall(method, true); + .Ldloc(priorityLocal.Local); + + EmitSetStyledPropertyValue(emitter); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + emitter.Ldsfld(AvaloniaProperty); + context.Emit(arguments[1], emitter, Parameters[1]); + context.Emit(arguments[0], emitter, Parameters[0]); + EmitSetStyledPropertyValue(emitter); + } + + private void EmitSetStyledPropertyValue(IXamlILEmitter emitter) + { + var method = Types.AvaloniaObjectSetStyledPropertyValue.MakeGenericMethod(new[] { Parameters[1] }); + emitter.EmitCall(method, true); } } class UnsetValueSetter : AvaloniaPropertyCustomSetter { public UnsetValueSetter(AvaloniaXamlIlWellKnownTypes types, IXamlType declaringType, IXamlField avaloniaProperty) - : base(types, declaringType, avaloniaProperty) + : base(types, declaringType, avaloniaProperty, false) { - Parameters = new[] {types.UnsetValueType}; + Parameters = new[] { types.UnsetValueType }; } public override void Emit(IXamlILEmitter codegen) { + codegen.Pop(); + EmitSetValue(codegen); + } + + public override void EmitWithArguments( + XamlEmitContextWithLocals context, + IXamlILEmitter emitter, + IReadOnlyList arguments) + { + EmitSetValue(emitter); + } + + private void EmitSetValue(IXamlILEmitter emitter) + { + // Ignore the instance and load one from the static field to avoid extra local variable var unsetValue = Types.AvaloniaProperty.Fields.First(f => f.Name == "UnsetValue"); - codegen - // Ignore the instance and load one from the static field to avoid extra local variable - .Pop() + + emitter .Ldsfld(AvaloniaProperty) .Ldsfld(unsetValue) .Ldc_I4(0) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index a4e6be2d14..c1c0594ec2 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit a4e6be2d1407abec4f35fcb208848830ce513ead +Subproject commit c1c0594ec2c35b08988183b1a5b3e34dfa19179d From 4497b8a7306e37916c30f5151285d45dcb2e9bf5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 5 Aug 2022 12:50:01 +0200 Subject: [PATCH 025/453] Hackfix for #8678. --- .../StaticResourceExtension.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index add97a660b..d462a2210e 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -39,6 +39,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions targetType = setter.Property.PropertyType; } + var previousWasControlTheme = false; + // Look upwards though the ambient context for IResourceNodes // which might be able to give us the resource. foreach (var parent in stack.Parents) @@ -47,6 +49,21 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions { return ColorToBrushConverter.Convert(value, targetType); } + + // HACK: Temporary fix for #8678. Hard-coded to only work for the DevTools main + // window as we don't want 3rd parties to start relying on this hack. + // + // We need to implement compile-time merging of resource dictionaries and this + // hack can be removed. + if (previousWasControlTheme && + parent is ResourceDictionary hack && + hack.Owner?.GetType().FullName == "Avalonia.Diagnostics.Views.MainWindow" && + hack.Owner.TryGetResource(ResourceKey, out value)) + { + return ColorToBrushConverter.Convert(value, targetType); + } + + previousWasControlTheme = parent is ControlTheme; } if (provideTarget.TargetObject is IControl target && @@ -69,3 +86,4 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions } } } + From a9cb1e2497a3104ecdd34318842821e67723aa68 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Fri, 5 Aug 2022 14:56:16 +0300 Subject: [PATCH 026/453] Address review. --- .../Converters/StringFormatConverter.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/Converters/StringFormatConverter.cs b/src/Avalonia.Controls/Converters/StringFormatConverter.cs index 7075d3de99..82e75d9c34 100644 --- a/src/Avalonia.Controls/Converters/StringFormatConverter.cs +++ b/src/Avalonia.Controls/Converters/StringFormatConverter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using Avalonia.Data.Converters; namespace Avalonia.Controls.Converters; @@ -13,19 +14,11 @@ public class StringFormatConverter : IMultiValueConverter { public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { - if (values != null && - values.Count == 5 && - values[0] is string format && - values[1] is double && - values[2] is double && - values[3] is double && - values[4] is double) - + if (values[0] is string format) { - try { - return string.Format(format, values[1], values[2], values[3], values[4]); + return string.Format(format, values.Skip(1).ToArray()); } catch { From 3af34590745c83e282331093858f25fef0e641af Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 6 Aug 2022 00:57:43 -0400 Subject: [PATCH 027/453] Stop transition if it was cancelled --- src/Avalonia.Controls/TransitioningContentControl.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs index 451e234653..70b21b7248 100644 --- a/src/Avalonia.Controls/TransitioningContentControl.cs +++ b/src/Avalonia.Controls/TransitioningContentControl.cs @@ -84,13 +84,19 @@ public class TransitioningContentControl : ContentControl _lastTransitionCts?.Cancel(); _lastTransitionCts = new CancellationTokenSource(); + var localToken = _lastTransitionCts.Token; if (PageTransition != null) - await PageTransition.Start(this, null, true, _lastTransitionCts.Token); + await PageTransition.Start(this, null, true, localToken); + + if (localToken.IsCancellationRequested) + { + return; + } CurrentContent = content; if (PageTransition != null) - await PageTransition.Start(null, this, true, _lastTransitionCts.Token); + await PageTransition.Start(null, this, true, localToken); } } From 2132ba2cc909884fb069b384f5ac30fce7cc63b5 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 6 Aug 2022 03:58:45 -0400 Subject: [PATCH 028/453] Delete legacy ReactiveUI.TransitioningContentControl --- .../TransitioningContentControl.cs | 80 ------------------- 1 file changed, 80 deletions(-) delete mode 100644 src/Avalonia.ReactiveUI/TransitioningContentControl.cs diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs deleted file mode 100644 index d26e90b2da..0000000000 --- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Threading; - -using Avalonia.Animation; -using Avalonia.Controls; -using Avalonia.Styling; - -namespace Avalonia.ReactiveUI -{ - /// - /// A ContentControl that animates the transition when its content is changed. - /// - [Obsolete("Use TransitioningContentControl in Avalonia.Controls namespace")] - public class TransitioningContentControl : ContentControl, IStyleable - { - /// - /// for the property. - /// - public static readonly StyledProperty PageTransitionProperty = - AvaloniaProperty.Register(nameof(PageTransition), - new CrossFade(TimeSpan.FromSeconds(0.5))); - - /// - /// for the property. - /// - public static readonly StyledProperty DefaultContentProperty = - AvaloniaProperty.Register(nameof(DefaultContent)); - - private CancellationTokenSource? _lastTransitionCts; - - /// - /// Gets or sets the animation played when content appears and disappears. - /// - public IPageTransition? PageTransition - { - get => GetValue(PageTransitionProperty); - set => SetValue(PageTransitionProperty, value); - } - - /// - /// Gets or sets the content displayed whenever there is no page currently routed. - /// - public object? DefaultContent - { - get => GetValue(DefaultContentProperty); - set => SetValue(DefaultContentProperty, value); - } - - /// - /// Gets or sets the content with animation. - /// - public new object? Content - { - get => base.Content; - set => UpdateContentWithTransition(value); - } - - /// - /// TransitioningContentControl uses the default ContentControl - /// template from Avalonia default theme. - /// - Type IStyleable.StyleKey => typeof(ContentControl); - - /// - /// Updates the content with transitions. - /// - /// New content to set. - private async void UpdateContentWithTransition(object? content) - { - _lastTransitionCts?.Cancel(); - _lastTransitionCts = new CancellationTokenSource(); - - if (PageTransition != null) - await PageTransition.Start(this, null, true, _lastTransitionCts.Token); - base.Content = content; - if (PageTransition != null) - await PageTransition.Start(null, this, true, _lastTransitionCts.Token); - } - } -} From a4c220cdcc6021f5a7d8b3cb805ff108e5eff31f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 6 Aug 2022 04:45:22 -0400 Subject: [PATCH 029/453] Enable android composition renderer --- src/Android/Avalonia.Android/AndroidPlatform.cs | 15 +++++++++++++-- .../Platform/SkiaPlatform/TopLevelImpl.cs | 9 ++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 0c72c389dc..89444dea10 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -8,7 +8,8 @@ using Avalonia.Input.Platform; using Avalonia.OpenGL.Egl; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Skia; +using Avalonia.Rendering.Composition; +using Avalonia.OpenGL; namespace Avalonia { @@ -42,6 +43,8 @@ namespace Avalonia.Android public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); + internal static Compositor Compositor { get; private set; } + public static void Initialize(AndroidPlatformOptions options) { Options = options; @@ -62,12 +65,20 @@ namespace Avalonia.Android { EglPlatformOpenGlInterface.TryInitialize(); } + + if (options.UseCompositor) + { + Compositor = new Compositor( + AvaloniaLocator.Current.GetRequiredService(), + AvaloniaLocator.Current.GetService()); + } } } public sealed class AndroidPlatformOptions { - public bool UseDeferredRendering { get; set; } = true; + public bool UseDeferredRendering { get; set; } = false; public bool UseGpu { get; set; } = true; + public bool UseCompositor { get; set; } = true; } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index fd97e293f9..3484ea2317 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -19,6 +19,7 @@ using Avalonia.OpenGL.Surfaces; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Rendering; +using Avalonia.Rendering.Composition; namespace Avalonia.Android.Platform.SkiaPlatform { @@ -84,9 +85,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IEnumerable Surfaces => new object[] { _gl, _framebuffer, Handle }; public IRenderer CreateRenderer(IRenderRoot root) => - AndroidPlatform.Options.UseDeferredRendering - ? new DeferredRenderer(root, AvaloniaLocator.Current.GetService()) { RenderOnlyOnRenderThread = true } - : new ImmediateRenderer(root); + AndroidPlatform.Options.UseCompositor + ? new CompositingRenderer(root, AndroidPlatform.Compositor) { DrawFps = true } + : AndroidPlatform.Options.UseDeferredRendering + ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true } + : new ImmediateRenderer(root); public virtual void Hide() { From 8100cd3571ad4700d324dbeb393b69d6b450c9de Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 6 Aug 2022 21:39:59 -0400 Subject: [PATCH 030/453] Optimize CurrentThreadIsLoopThread on android backend --- .../AndroidThreadingInterface.cs | 17 ++++++++++++++++- .../Avalonia.Android/Avalonia.Android.csproj | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index e72f0aed90..abc7947f38 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -14,6 +14,7 @@ namespace Avalonia.Android internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface { private Handler _handler; + private static Thread s_uiThread; public AndroidThreadingInterface() { @@ -76,7 +77,21 @@ namespace Avalonia.Android EnsureInvokeOnMainThread(() => Signaled?.Invoke(null)); } - public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread()); + public bool CurrentThreadIsLoopThread + { + get + { + if (s_uiThread != null) + return s_uiThread == Thread.CurrentThread; + + if (Looper.MainLooper.IsCurrentThread) + { + s_uiThread = Thread.CurrentThread; + return true; + } + return false; + } + } public event Action Signaled; } } diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 6688dde8f5..082ba1f647 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -1,7 +1,7 @@  net6.0-android - 21 + 23 true true portable From 748a25df0d8210c070c07dd9e0cd7a95ffa12e5f Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 6 Aug 2022 22:38:36 -0400 Subject: [PATCH 031/453] Restore missing DefaultContentProperty prop --- src/Avalonia.ReactiveUI/RoutedViewHost.cs | 15 +++++++++++++++ src/Avalonia.ReactiveUI/ViewModelViewHost.cs | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 9269dc70f8..775014d419 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -64,6 +64,12 @@ namespace Avalonia.ReactiveUI public static readonly StyledProperty ViewContractProperty = AvaloniaProperty.Register(nameof(ViewContract)); + /// + /// for the property. + /// + public static readonly StyledProperty DefaultContentProperty = + ViewModelViewHost.DefaultContentProperty.AddOwner(); + /// /// Initializes a new instance of the class. /// @@ -106,6 +112,15 @@ namespace Avalonia.ReactiveUI set => SetValue(ViewContractProperty, value); } + /// + /// Gets or sets the content displayed whenever there is no page currently routed. + /// + public object? DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + /// /// Gets or sets the ReactiveUI view locator used by this router. /// diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index 16dee00ebc..869238b377 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -1,5 +1,8 @@ using System; using System.Reactive.Disposables; + +using Avalonia.Controls; + using ReactiveUI; using Splat; @@ -24,6 +27,12 @@ namespace Avalonia.ReactiveUI public static readonly StyledProperty ViewContractProperty = AvaloniaProperty.Register(nameof(ViewContract)); + /// + /// for the property. + /// + public static readonly StyledProperty DefaultContentProperty = + AvaloniaProperty.Register(nameof(DefaultContent)); + /// /// Initializes a new instance of the class. /// @@ -55,6 +64,15 @@ namespace Avalonia.ReactiveUI set => SetValue(ViewContractProperty, value); } + /// + /// Gets or sets the content displayed whenever there is no page currently routed. + /// + public object? DefaultContent + { + get => GetValue(DefaultContentProperty); + set => SetValue(DefaultContentProperty, value); + } + /// /// Gets or sets the view locator. /// From 0e733c70a16e8e465f2691800aed1a350c9fdf47 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sat, 6 Aug 2022 22:48:59 -0400 Subject: [PATCH 032/453] Support 21 api --- src/Android/Avalonia.Android/AndroidThreadingInterface.cs | 6 +++++- src/Android/Avalonia.Android/Avalonia.Android.csproj | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index abc7947f38..42f75a27e1 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -84,11 +84,15 @@ namespace Avalonia.Android if (s_uiThread != null) return s_uiThread == Thread.CurrentThread; - if (Looper.MainLooper.IsCurrentThread) + var isOnMainThread = OperatingSystem.IsAndroidVersionAtLeast(23) + ? Looper.MainLooper.IsCurrentThread + : Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread()); + if (isOnMainThread) { s_uiThread = Thread.CurrentThread; return true; } + return false; } } diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 082ba1f647..6688dde8f5 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -1,7 +1,7 @@  net6.0-android - 23 + 21 true true portable From ed582a9fde3aef0a2cd9462cbe66c9dc1446076d Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Wed, 3 Aug 2022 22:39:40 -0400 Subject: [PATCH 033/453] Implement DataContext update notifications on the DataGrid This fixes #5661 by allowing the DataGrid to notify its cells that their DataContext is about to be changed. --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index d42468f47e..f4cd425c53 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -2167,6 +2167,38 @@ namespace Avalonia.Controls return desiredSize; } + + /// + protected override void OnDataContextBeginUpdate() + { + base.OnDataContextBeginUpdate(); + foreach (DataGridRow row in GetAllRows()) + { + foreach (DataGridCell cell in row.Cells) + { + if (cell.Content is StyledElement) + { + DataContextProperty.Notifying?.Invoke((IAvaloniaObject)cell.Content, true); + } + } + } + } + + /// + protected override void OnDataContextEndUpdate() + { + base.OnDataContextEndUpdate(); + foreach (DataGridRow row in GetAllRows()) + { + foreach (DataGridCell cell in row.Cells) + { + if (cell.Content is StyledElement) + { + DataContextProperty.Notifying?.Invoke((IAvaloniaObject)cell.Content, false); + } + } + } + } /// /// Raises the BeginningEdit event. From 8436e0f7624a050b7e256abcf7d7a1d8f5aae30d Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 7 Aug 2022 01:32:48 -0400 Subject: [PATCH 034/453] Remove test that doesn't make sense anymore --- .../TransitioningContentControlTest.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs index 6ca617d2d4..daa3830b7d 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/TransitioningContentControlTest.cs @@ -12,14 +12,6 @@ namespace Avalonia.ReactiveUI.UnitTests { public class TransitioningContentControlTest { - [Fact] - public void Transitioning_Control_Should_Derive_Template_From_Content_Control() - { - var target = new TransitioningContentControl(); - var stylable = (IStyledElement)target; - Assert.Equal(typeof(ContentControl),stylable.StyleKey); - } - [Fact] public void Transitioning_Control_Template_Should_Be_Instantiated() { From 9377ce2a8fa630de33373d8beda3392d2e1dcb06 Mon Sep 17 00:00:00 2001 From: Steve Date: Sun, 7 Aug 2022 15:23:51 +0900 Subject: [PATCH 035/453] Update Visual.cs --- src/Avalonia.Base/Visual.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index fbc940114a..8feba116f0 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -667,14 +667,8 @@ namespace Avalonia if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { - var root = this.FindAncestorOfType(); - if (root is null) - { - Logger.TryGet(LogEventLevel.Error, "Visual")?.Log("Visual", - "Visual is atached to visual tree but root could not be found."); - return; - } - + var root = this.FindAncestorOfType() ?? + throw new AvaloniaInternalException("Visual is atached to visual tree but root could not be found."); var e = new VisualTreeAttachmentEventArgs(_visualParent, root); OnAttachedToVisualTreeCore(e); } From c0f4e725c9b9ef56b3cda611941a6498b25c2cf5 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 7 Aug 2022 03:27:15 -0400 Subject: [PATCH 036/453] Disable hardcoded FPS --- .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 3484ea2317..dc74214170 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -86,7 +86,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IRenderer CreateRenderer(IRenderRoot root) => AndroidPlatform.Options.UseCompositor - ? new CompositingRenderer(root, AndroidPlatform.Compositor) { DrawFps = true } + ? new CompositingRenderer(root, AndroidPlatform.Compositor) : AndroidPlatform.Options.UseDeferredRendering ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true } : new ImmediateRenderer(root); From 8e64d69e8316d09fe7e7c9fc88f2255b7e5acbbd Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 8 Aug 2022 01:58:45 -0400 Subject: [PATCH 037/453] Fix control catalog crash --- samples/ControlCatalog/Pages/PointersPage.xaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/Pages/PointersPage.xaml.cs b/samples/ControlCatalog/Pages/PointersPage.xaml.cs index 6fc468e37f..e82fcb6226 100644 --- a/samples/ControlCatalog/Pages/PointersPage.xaml.cs +++ b/samples/ControlCatalog/Pages/PointersPage.xaml.cs @@ -59,7 +59,7 @@ Position: ??? ???"; e.Pointer.Capture(null); e.Handled = true; } - else + else if (e.Pointer.Captured is not null) { throw new InvalidOperationException("How?"); } From 37dab7a565b047ff330fb2534bc531d30b43e3ce Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 8 Aug 2022 02:18:44 -0400 Subject: [PATCH 038/453] Minor scroll gesture recognizer fixes --- .../Input/GestureRecognizers/ScrollGestureRecognizer.cs | 3 +-- src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 889b7e3b82..0fb991eaac 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -66,8 +66,7 @@ namespace Avalonia.Input.GestureRecognizers public void PointerPressed(PointerPressedEventArgs e) { - if (e.Pointer.IsPrimary && - (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + if (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen) { EndGesture(); _tracking = e.Pointer; diff --git a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs index 0e4e0ed3e2..b8f6f99ae8 100644 --- a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs @@ -137,9 +137,13 @@ namespace Avalonia.Input.Raw /// public Point Position { get; set; } + /// public float Twist { get; set; } + /// public float Pressure { get; set; } + /// public float XTilt { get; set; } + /// public float YTilt { get; set; } From 8716a5111acfd67d07585053d4564870d314f1b0 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 8 Aug 2022 03:00:51 -0400 Subject: [PATCH 039/453] Revamp android input handling to support mouse, pen and historical points --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 17 +- .../Helpers/AndroidMotionEventsHelper.cs | 251 ++++++++++++++++++ .../Helpers/AndroidTouchEventsHelper.cs | 85 ------ 3 files changed, 264 insertions(+), 89 deletions(-) create mode 100644 src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs delete mode 100644 src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index dc74214170..17a0d6b63a 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; + using Android.Content; using Android.Graphics; using Android.Views; @@ -30,7 +31,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly IFramebufferPlatformSurface _framebuffer; private readonly AndroidKeyboardEventsHelper _keyboardHelper; - private readonly AndroidTouchEventsHelper _touchHelper; + private readonly AndroidMotionEventsHelper _pointerHelper; private readonly ITextInputMethodImpl _textInputMethod; private ViewImpl _view; @@ -39,8 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view = new ViewImpl(avaloniaView.Context, this, placeOnTop); _textInputMethod = new AndroidInputMethod(_view); _keyboardHelper = new AndroidKeyboardEventsHelper(this); - _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, - GetAvaloniaPointFromEvent); + _pointerHelper = new AndroidMotionEventsHelper(this); _gl = GlPlatformSurface.TryCreate(this); _framebuffer = new FramebufferManager(this); @@ -160,10 +160,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform _tl.Draw(); } + protected override bool DispatchGenericPointerEvent(MotionEvent e) + { + bool callBase; + bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase); + bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false; + + return result != null ? result.Value : baseResult; + } + public override bool DispatchTouchEvent(MotionEvent e) { bool callBase; - bool? result = _tl._touchHelper.DispatchTouchEvent(e, out callBase); + bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase); bool baseResult = callBase ? base.DispatchTouchEvent(e) : false; return result != null ? result.Value : baseResult; diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs new file mode 100644 index 0000000000..ce385ebe34 --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; + +using Android.Views; + +using Avalonia.Android.Platform.SkiaPlatform; +using Avalonia.Collections.Pooled; +using Avalonia.Input; +using Avalonia.Input.Raw; + +#nullable enable + +namespace Avalonia.Android.Platform.Specific.Helpers +{ + internal class AndroidMotionEventsHelper : IDisposable + { + private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never); + private static readonly float s_radiansToDegree = (float)(180f * Math.PI); + private readonly TouchDevice _touchDevice; + private readonly MouseDevice _mouseDevice; + private readonly PenDevice _penDevice; + private readonly TopLevelImpl _view; + private bool _disposed; + + public AndroidMotionEventsHelper(TopLevelImpl view) + { + _touchDevice = new TouchDevice(); + _penDevice = new PenDevice(); + _mouseDevice = new MouseDevice(); + _view = view; + } + + public bool? DispatchMotionEvent(MotionEvent e, out bool callBase) + { + callBase = true; + if (_disposed) + { + return null; + } + + var eventTime = (ulong)DateTime.Now.Millisecond; + var inputRoot = _view.InputRoot; + var actionMasked = e.ActionMasked; + var modifiers = GetModifiers(e.MetaState, e.ButtonState); + + if (actionMasked == MotionEventActions.Move) + { + for (int index = 0; index < e.PointerCount; index++) + { + var toolType = e.GetToolType(index); + var device = GetDevice(toolType); + var eventType = toolType == MotionEventToolType.Finger ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move; + var point = CreatePoint(e, index); + modifiers |= GetToolModifiers(toolType); + + // ButtonState reports only mouse buttons, but not touch or stylus pointer. + if (toolType != MotionEventToolType.Mouse) + { + modifiers |= RawInputModifiers.LeftMouseButton; + } + + var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index)) + { + IntermediatePoints = new Lazy?>(() => + { + var site = e.HistorySize; + s_intermediatePointsPooledList.Clear(); + s_intermediatePointsPooledList.Capacity = site; + + for (int pos = 0; pos < site; pos++) + { + s_intermediatePointsPooledList.Add(CreateHistoricalPoint(e, index, pos)); + } + + return s_intermediatePointsPooledList; + }) + }; + _view.Input(args); + } + } + else + { + var index = e.ActionIndex; + var toolType = e.GetToolType(index); + var device = GetDevice(toolType); + modifiers |= GetToolModifiers(toolType); + var point = CreatePoint(e, index); + + if (actionMasked == MotionEventActions.Scroll && toolType == MotionEventToolType.Mouse) + { + var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll)); + var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None); + _view.Input(args); + } + else + { + var eventType = GetActionType(e, actionMasked, toolType); + if (eventType >= 0) + { + var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index)); + _view.Input(args); + } + } + } + + return true; + } + + private static RawInputModifiers GetModifiers(MetaKeyStates metaState, MotionEventButtonState buttonState) + { + var modifiers = RawInputModifiers.None; + if (metaState.HasAnyFlag(MetaKeyStates.ShiftOn)) + { + modifiers |= RawInputModifiers.Shift; + } + if (metaState.HasAnyFlag(MetaKeyStates.CtrlOn)) + { + modifiers |= RawInputModifiers.Control; + } + if (metaState.HasAnyFlag(MetaKeyStates.AltOn)) + { + modifiers |= RawInputModifiers.Alt; + } + if (metaState.HasAnyFlag(MetaKeyStates.MetaOn)) + { + modifiers |= RawInputModifiers.Meta; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Primary)) + { + modifiers |= RawInputModifiers.LeftMouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Secondary)) + { + modifiers |= RawInputModifiers.RightMouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Tertiary)) + { + modifiers |= RawInputModifiers.MiddleMouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Back)) + { + modifiers |= RawInputModifiers.XButton1MouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.Forward)) + { + modifiers |= RawInputModifiers.XButton2MouseButton; + } + if (buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary)) + { + modifiers |= RawInputModifiers.PenBarrelButton; + } + return modifiers; + } + +#pragma warning disable CA1416 // Validate platform compatibility + private static RawPointerEventType GetActionType(MotionEvent e, MotionEventActions actionMasked, MotionEventToolType toolType) + { + var isTouch = toolType == MotionEventToolType.Finger; + var isMouse = toolType == MotionEventToolType.Mouse; + switch (actionMasked) + { + // DOWN + case MotionEventActions.Down when !isMouse: + case MotionEventActions.PointerDown when !isMouse: + return isTouch ? RawPointerEventType.TouchBegin : RawPointerEventType.LeftButtonDown; + case MotionEventActions.ButtonPress: + return e.ActionButton switch + { + MotionEventButtonState.Back => RawPointerEventType.XButton1Down, + MotionEventButtonState.Forward => RawPointerEventType.XButton2Down, + MotionEventButtonState.Primary => RawPointerEventType.LeftButtonDown, + MotionEventButtonState.Secondary => RawPointerEventType.RightButtonDown, + MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonDown, + MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonDown, + MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonDown, + _ => RawPointerEventType.LeftButtonDown + }; + // UP + case MotionEventActions.Up when !isMouse: + case MotionEventActions.PointerUp when !isMouse: + return isTouch ? RawPointerEventType.TouchEnd : RawPointerEventType.LeftButtonUp; + case MotionEventActions.ButtonRelease: + return e.ActionButton switch + { + MotionEventButtonState.Back => RawPointerEventType.XButton1Up, + MotionEventButtonState.Forward => RawPointerEventType.XButton2Up, + MotionEventButtonState.Primary => RawPointerEventType.LeftButtonUp, + MotionEventButtonState.Secondary => RawPointerEventType.RightButtonUp, + MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonUp, + MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonUp, + MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonUp, + _ => RawPointerEventType.LeftButtonUp + }; + // MOVE + case MotionEventActions.Outside: + case MotionEventActions.HoverMove: + case MotionEventActions.Move: + return isTouch ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move; + // CANCEL + case MotionEventActions.Cancel: + return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow; + default: + return (RawPointerEventType)(-1); + } + } +#pragma warning restore CA1416 // Validate platform compatibility + + private IPointerDevice GetDevice(MotionEventToolType type) + { + return type switch + { + MotionEventToolType.Mouse => _mouseDevice, + MotionEventToolType.Stylus => _penDevice, + MotionEventToolType.Eraser => _penDevice, + MotionEventToolType.Finger => _touchDevice, + _ => _touchDevice + }; + } + + private RawPointerPoint CreatePoint(MotionEvent e, int index) + { + return new RawPointerPoint + { + Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling, + Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices + Twist = e.GetOrientation(index) * s_radiansToDegree + }; + } + + private RawPointerPoint CreateHistoricalPoint(MotionEvent e, int index, int pos) + { + return new RawPointerPoint + { + Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling, + Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1), + Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree + }; + } + + private static RawInputModifiers GetToolModifiers(MotionEventToolType toolType) + { + // Android "Eraser" indicates Inverted pen OR actual Eraser. So we have to go both here. + return toolType == MotionEventToolType.Eraser ? RawInputModifiers.PenInverted | RawInputModifiers.PenEraser : RawInputModifiers.None; + } + + public void Dispose() + { + _disposed = true; + } + } +} diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs deleted file mode 100644 index 6142598514..0000000000 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using Android.Views; -using Avalonia.Input; -using Avalonia.Input.Raw; -using Avalonia.Platform; - -namespace Avalonia.Android.Platform.Specific.Helpers -{ - public class AndroidTouchEventsHelper : IDisposable where TView : ITopLevelImpl, IAndroidView - { - private TView _view; - public bool HandleEvents { get; set; } - - public AndroidTouchEventsHelper(TView view, Func getInputRoot, Func getPointfunc) - { - this._view = view; - HandleEvents = true; - _getPointFunc = getPointfunc; - _getInputRoot = getInputRoot; - } - - private TouchDevice _touchDevice = new TouchDevice(); - private Func _getPointFunc; - private Func _getInputRoot; - - public bool? DispatchTouchEvent(MotionEvent e, out bool callBase) - { - if (!HandleEvents) - { - callBase = true; - return null; - } - - var eventTime = DateTime.Now; - - //Basic touch support - var pointerEventType = e.Action switch - { - MotionEventActions.Down => RawPointerEventType.TouchBegin, - MotionEventActions.Up => RawPointerEventType.TouchEnd, - MotionEventActions.Cancel => RawPointerEventType.TouchCancel, - _ => RawPointerEventType.TouchUpdate - }; - - if (e.Action.HasFlag(MotionEventActions.PointerDown)) - { - pointerEventType = RawPointerEventType.TouchBegin; - } - - if (e.Action.HasFlag(MotionEventActions.PointerUp)) - { - pointerEventType = RawPointerEventType.TouchEnd; - } - - for (int i = 0; i < e.PointerCount; i++) - { - //if point is in view otherwise it's possible avalonia not to find the proper window to dispatch the event - var point = _getPointFunc(e, i); - - double x = _view.View.GetX(); - double y = _view.View.GetY(); - double r = x + _view.View.Width; - double b = y + _view.View.Height; - - if (x <= point.X && r >= point.X && y <= point.Y && b >= point.Y) - { - var inputRoot = _getInputRoot(); - - var mouseEvent = new RawTouchEventArgs(_touchDevice, (uint)eventTime.Ticks, inputRoot, - i == e.ActionIndex ? pointerEventType : RawPointerEventType.TouchUpdate, point, RawInputModifiers.None, e.GetPointerId(i)); - _view.Input(mouseEvent); - } - } - - callBase = true; - //if return false events for move and up are not received!!! - return e.Action != MotionEventActions.Up; - } - - public void Dispose() - { - HandleEvents = false; - } - } -} From ee662cecdd0c9b57f986d44e93afa26b5d524b3e Mon Sep 17 00:00:00 2001 From: ili Date: Mon, 8 Aug 2022 20:13:47 +0500 Subject: [PATCH 040/453] Avoid lock in timers --- .../AndroidThreadingInterface.cs | 45 +++++++---------- .../InternalPlatformThreadingInterface.cs | 4 +- .../HeadlessPlatformThreadingInterface.cs | 48 +++++++++---------- 3 files changed, 42 insertions(+), 55 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index 42f75a27e1..de9149e9a1 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -27,46 +27,33 @@ namespace Avalonia.Android { if (interval.TotalMilliseconds < 10) interval = TimeSpan.FromMilliseconds(10); - object l = new object(); + var stopped = false; Timer timer = null; - var scheduled = false; timer = new Timer(_ => { - lock (l) + if (stopped) + return; + + EnsureInvokeOnMainThread(() => { - if (stopped) + try { - timer.Dispose(); - return; + tick(); } - if (scheduled) - return; - scheduled = true; - EnsureInvokeOnMainThread(() => + finally { - try - { - tick(); - } - finally - { - lock (l) - { - scheduled = false; - } - } - }); - } - }, null, TimeSpan.Zero, interval); + if (!stopped) + timer.Change(interval, Timeout.InfiniteTimeSpan); + } + }); + }, + null, interval, Timeout.InfiniteTimeSpan); return Disposable.Create(() => { - lock (l) - { - stopped = true; - timer.Dispose(); - } + stopped = true; + timer.Dispose(); }); } diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs index 630d2d8efb..e1f6db9c60 100644 --- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs +++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs @@ -43,7 +43,7 @@ namespace Avalonia.Controls.Platform _priority = priority; _interval = interval; _tick = tick; - _timer = new Timer(OnTimer, null, interval, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(OnTimer, null, interval, Timeout.InfiniteTimeSpan); _handle = GCHandle.Alloc(_timer); } @@ -57,7 +57,7 @@ namespace Avalonia.Controls.Platform if (_timer == null) return; _tick(); - _timer?.Change(_interval, TimeSpan.FromMilliseconds(-1)); + _timer?.Change(_interval, Timeout.InfiniteTimeSpan); }); } diff --git a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs index e42a7b1a71..b233b46dd0 100644 --- a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs @@ -36,35 +36,35 @@ namespace Avalonia.Headless public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) { - var cancelled = false; - var enqueued = false; - var l = new object(); - var timer = new Timer(_ => + if (interval.TotalMilliseconds < 10) + interval = TimeSpan.FromMilliseconds(10); + + var stopped = false; + Timer timer = null; + timer = new Timer(_ => { - lock (l) + if (stopped) + return; + + Dispatcher.UIThread.Post(() => { - if (cancelled || enqueued) - return; - enqueued = true; - Dispatcher.UIThread.Post(() => + try { - lock (l) - { - enqueued = false; - if (cancelled) - return; - tick(); - } - }, priority); - } - }, null, interval, interval); + tick(); + } + finally + { + if (!stopped) + timer.Change(interval, Timeout.InfiniteTimeSpan); + } + }); + }, + null, interval, Timeout.InfiniteTimeSpan); + return Disposable.Create(() => { - lock (l) - { - timer.Dispose(); - cancelled = true; - } + stopped = true; + timer.Dispose(); }); } From f009c316e208bc7ade2159b10f4501501732f65d Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 8 Aug 2022 17:24:15 +0200 Subject: [PATCH 041/453] Imm32 preedit --- .../Presenters/TextPresenter.cs | 144 ++++++------ src/Avalonia.Controls/TextBox.cs | 2 +- .../Avalonia.Win32/Input/Imm32CaretManager.cs | 8 +- .../Avalonia.Win32/Input/Imm32InputMethod.cs | 205 ++++++++++++------ .../Interop/UnmanagedMethods.cs | 71 +++++- .../Avalonia.Win32/WindowImpl.AppWndProc.cs | 44 ++-- 6 files changed, 303 insertions(+), 171 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 3f4d881735..8b82530022 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty CaretBrushProperty = AvaloniaProperty.Register(nameof(CaretBrush)); - + public static readonly DirectProperty SelectionStartProperty = TextBox.SelectionStartProperty.AddOwner( o => o.SelectionStart, @@ -44,7 +44,7 @@ namespace Avalonia.Controls.Presenters TextBox.SelectionEndProperty.AddOwner( o => o.SelectionEnd, (o, v) => o.SelectionEnd = v); - + /// /// Defines the property. /// @@ -80,7 +80,7 @@ namespace Avalonia.Controls.Presenters /// public static readonly StyledProperty LineHeightProperty = TextBlock.LineHeightProperty.AddOwner(); - + /// /// Defines the property. /// @@ -114,7 +114,7 @@ namespace Avalonia.Controls.Presenters } public event EventHandler? CaretBoundsChanged; - + /// /// Gets or sets a brush used to paint the control's background. /// @@ -202,7 +202,7 @@ namespace Avalonia.Controls.Presenters get => GetValue(TextWrappingProperty); set => SetValue(TextWrappingProperty, value); } - + /// /// Gets or sets the line height. By default, this is set to , which determines the appropriate height automatically. /// @@ -232,11 +232,11 @@ namespace Avalonia.Controls.Presenters { return _textLayout; } - + _textLayout = CreateTextLayout(); UpdateCaret(_lastCharacterHit); - + return _textLayout; } } @@ -285,7 +285,7 @@ namespace Avalonia.Controls.Presenters get => GetValue(CaretBrushProperty); set => SetValue(CaretBrushProperty, value); } - + public int SelectionStart { get @@ -313,7 +313,7 @@ namespace Avalonia.Controls.Presenters SetAndRaise(SelectionEndProperty, ref _selectionEnd, value); } } - + protected override bool BypassFlowDirectionPolicies => true; /// @@ -330,9 +330,9 @@ namespace Avalonia.Controls.Presenters var foreground = Foreground; var maxWidth = MathUtilities.IsZero(constraint.Width) ? double.PositiveInfinity : constraint.Width; var maxHeight = MathUtilities.IsZero(constraint.Height) ? double.PositiveInfinity : constraint.Height; - + var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, - TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, + TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, flowDirection: FlowDirection, lineHeight: LineHeight); return textLayout; @@ -398,7 +398,7 @@ namespace Avalonia.Controls.Presenters { return; } - + var caretBrush = CaretBrush?.ToImmutable(); if (caretBrush is null) @@ -423,13 +423,13 @@ namespace Avalonia.Controls.Presenters context.DrawLine(new ImmutablePen(caretBrush), p1, p2); } - + private (Point, Point) GetCaretPoints() { var x = Math.Floor(_caretBounds.X) + 0.5; var y = Math.Floor(_caretBounds.Y) + 0.5; var b = Math.Ceiling(_caretBounds.Bottom) - 0.5; - + var caretIndex = _lastCharacterHit.FirstCharacterIndex + _lastCharacterHit.TrailingLength; var lineIndex = TextLayout.GetLineIndexFromCharacterIndex(caretIndex, _lastCharacterHit.TrailingLength > 0); var textLine = TextLayout.TextLines[lineIndex]; @@ -438,7 +438,7 @@ namespace Avalonia.Controls.Presenters { x -= 1; } - + return (new Point(x, y), new Point(x, b)); } @@ -524,17 +524,7 @@ namespace Avalonia.Controls.Presenters var start = Math.Min(selectionStart, selectionEnd); var length = Math.Max(selectionStart, selectionEnd) - start; - List>? textStyleOverrides = null; - - if (length > 0 && SelectionForegroundBrush != null) - { - textStyleOverrides = new List> - { - new ValueSpan(start, length, - new GenericTextRunProperties(typeface, FontSize, - foregroundBrush: SelectionForegroundBrush)) - }; - } + IReadOnlyList>? textStyleOverrides = null; var foreground = Foreground; @@ -542,20 +532,26 @@ namespace Avalonia.Controls.Presenters { var preeditHighlight = new ValueSpan(_caretIndex, _preeditText.Length, new GenericTextRunProperties(typeface, FontSize, - foregroundBrush: foreground, - textDecorations: TextDecorations.Underline)); + foregroundBrush: Foreground, + textDecorations: TextDecorations.Underline, + backgroundBrush: SelectionBrush)); - if (textStyleOverrides == null) + textStyleOverrides = new[] + { + preeditHighlight + }; + } + else + { + if (length > 0 && SelectionForegroundBrush != null) { - textStyleOverrides = new List> + textStyleOverrides = new[] { - preeditHighlight + new ValueSpan(start, length, + new GenericTextRunProperties(typeface, FontSize, + foregroundBrush: SelectionForegroundBrush)) }; } - else - { - textStyleOverrides.Add(preeditHighlight); - } } if (PasswordChar != default(char) && !RevealPassword) @@ -574,7 +570,7 @@ namespace Avalonia.Controls.Presenters protected virtual void InvalidateTextLayout() { _textLayout = null; - + InvalidateMeasure(); } @@ -623,7 +619,7 @@ namespace Avalonia.Controls.Presenters private void CaretTimerTick(object? sender, EventArgs e) { _caretBlink = !_caretBlink; - + InvalidateVisual(); } @@ -633,7 +629,7 @@ namespace Avalonia.Controls.Presenters var textLine = TextLayout.TextLines[lineIndex]; var characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(textPosition)); - + var nextCaretCharacterHit = textLine.GetNextCaretCharacterHit(characterHit); if (nextCaretCharacterHit.FirstCharacterIndex <= textPosition) @@ -653,8 +649,8 @@ namespace Avalonia.Controls.Presenters _navigationPosition = _caretBounds.Position; CaretChanged(); - } - + } + public void MoveCaretToPoint(Point point) { var hit = TextLayout.HitTestPoint(point); @@ -685,7 +681,7 @@ namespace Avalonia.Controls.Presenters } var textLine = TextLayout.TextLines[lineIndex]; - + currentY += textLine.Height; } else @@ -701,9 +697,9 @@ namespace Avalonia.Controls.Presenters } var navigationPosition = _navigationPosition; - + MoveCaretToPoint(new Point(currentX, currentY)); - + _navigationPosition = navigationPosition.WithY(_caretBounds.Y); CaretChanged(); @@ -714,11 +710,11 @@ namespace Avalonia.Controls.Presenters if (Text is null) { return default; - } + } var characterHit = _lastCharacterHit; var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - + var lineIndex = TextLayout.GetLineIndexFromCharacterIndex(caretIndex, false); if (lineIndex < 0) @@ -740,11 +736,11 @@ namespace Avalonia.Controls.Presenters { characterHit = new CharacterHit(caretIndex); } - + if (caretIndex >= Text.Length) { characterHit = new CharacterHit(Text.Length); - + break; } @@ -756,10 +752,10 @@ namespace Avalonia.Controls.Presenters if (caretIndex <= CaretIndex) { lineIndex++; - + continue; } - + break; } } @@ -786,7 +782,7 @@ namespace Avalonia.Controls.Presenters return characterHit; } - + public void MoveCaretHorizontal(LogicalDirection direction = LogicalDirection.Forward) { if (FlowDirection == FlowDirection.RightToLeft) @@ -808,9 +804,9 @@ namespace Avalonia.Controls.Presenters private void UpdateCaret(CharacterHit characterHit) { _lastCharacterHit = characterHit; - + var caretIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - + var lineIndex = TextLayout.GetLineIndexFromCharacterIndex(caretIndex, characterHit.TrailingLength > 0); var textLine = TextLayout.TextLines[lineIndex]; var distanceX = textLine.GetDistanceFromCharacterHit(characterHit); @@ -825,7 +821,7 @@ namespace Avalonia.Controls.Presenters } var caretBounds = new Rect(distanceX, distanceY, 0, textLine.Height); - + if (caretBounds != _caretBounds) { _caretBounds = caretBounds; @@ -846,40 +842,40 @@ namespace Avalonia.Controls.Presenters base.OnDetachedFromVisualTree(e); _caretTimer.Stop(); - + _caretTimer.Tick -= CaretTimerTick; } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - + switch (change.Property.Name) { - case nameof (Foreground): - case nameof (FontSize): - case nameof (FontStyle): - case nameof (FontWeight): - case nameof (FontFamily): - case nameof (FontStretch): + case nameof(Foreground): + case nameof(FontSize): + case nameof(FontStyle): + case nameof(FontWeight): + case nameof(FontFamily): + case nameof(FontStretch): - case nameof (Text): - case nameof (PreeditText): - case nameof (TextAlignment): - case nameof (TextWrapping): + case nameof(Text): + case nameof(PreeditText): + case nameof(TextAlignment): + case nameof(TextWrapping): - case nameof (SelectionStart): - case nameof (SelectionEnd): - case nameof (SelectionForegroundBrush): + case nameof(SelectionStart): + case nameof(SelectionEnd): + case nameof(SelectionForegroundBrush): - case nameof (PasswordChar): - case nameof (RevealPassword): + case nameof(PasswordChar): + case nameof(RevealPassword): case nameof(FlowDirection): - { - InvalidateTextLayout(); - break; - } + { + InvalidateTextLayout(); + break; + } } } } diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 4c9e9327d4..20b11b3060 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1142,7 +1142,7 @@ namespace Avalonia.Controls protected override void OnPointerPressed(PointerPressedEventArgs e) { - if (_presenter == null) + if (_presenter == null || !string.IsNullOrEmpty(_presenter.PreeditText)) { return; } diff --git a/src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs b/src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs index 38605efa22..ca52bf6e67 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs @@ -7,14 +7,11 @@ namespace Avalonia.Win32.Input { private bool _isCaretCreated; - public void TryCreate(int _langId, IntPtr hwnd) + public void TryCreate(IntPtr hwnd) { if (!_isCaretCreated) { - if (_langId == LANG_ZH || _langId == LANG_JA) - { - _isCaretCreated = CreateCaret(hwnd, IntPtr.Zero, 2, 10); - } + _isCaretCreated = CreateCaret(hwnd, IntPtr.Zero, 2, 2); } } @@ -31,6 +28,7 @@ namespace Avalonia.Win32.Input if (_isCaretCreated) { DestroyCaret(); + _isCaretCreated = false; } } diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index 3adefd965f..9affb4fd5f 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -1,4 +1,6 @@ using System; +using System.Runtime.InteropServices; +using System.Text; using Avalonia.Input.TextInput; using Avalonia.Threading; @@ -12,102 +14,125 @@ namespace Avalonia.Win32.Input class Imm32InputMethod : ITextInputMethodImpl { public IntPtr HWND { get; private set; } - private IntPtr _defaultImc; + private IntPtr _currentHimc; private WindowImpl _parent; - private bool _active; - private bool _showCompositionWindow; + private ITextInputMethodClient _client; + private Imm32CaretManager _caretManager = new(); - private bool _showCandidateList; + private ushort _langId; private const int _caretMargin = 1; - - public void SetLanguageAndWindow(WindowImpl parent, IntPtr hwnd, IntPtr HKL) + + public bool IsActive => _client != null; + + public bool IsComposing { get; set; } + + public bool ShowCompositionWindow => false; + + public void CreateCaret() + { + _caretManager.TryCreate(HWND); + } + + public void EnableImm() { - if (HWND != hwnd) + var himc = ImmGetContext(HWND); + + if(himc == IntPtr.Zero) + { + himc = ImmCreateContext(); + } + + if(himc != _currentHimc) { - _defaultImc = IntPtr.Zero; + if(_currentHimc != IntPtr.Zero) + { + DisableImm(); + } + + ImmAssociateContext(HWND, himc); + + ImmReleaseContext(HWND, himc); + + _currentHimc = himc; + + _caretManager.TryCreate(HWND); } + } + + public void DisableImm() + { + _caretManager.TryDestroy(); + + Reset(); + + ImmAssociateContext(HWND, IntPtr.Zero); + + _caretManager.TryDestroy(); + + _currentHimc = IntPtr.Zero; + } + + public void SetLanguageAndWindow(WindowImpl parent, IntPtr hwnd, IntPtr HKL) + { HWND = hwnd; + _parent = parent; - _active = false; - _langId = PRIMARYLANGID(LGID(HKL)); - _showCompositionWindow = true; - _showCandidateList = true; - IsComposing = false; + var langId= PRIMARYLANGID(LGID(HKL)); + + if(langId != _langId) + { + DisableImm(); + } + + _langId = langId; + + EnableImm(); } public void ClearLanguageAndWindow() { - if (HWND != IntPtr.Zero && _defaultImc != IntPtr.Zero) - { - ImmReleaseContext(HWND, _defaultImc); - } + DisableImm(); - _defaultImc = IntPtr.Zero; HWND = IntPtr.Zero; _parent = null; - _active = false; + _client = null; _langId = 0; - _showCompositionWindow = false; - _showCandidateList = false; IsComposing = false; } //Dependant on CurrentThread. When Avalonia will support Multiple Dispatchers - //every Dispatcher should have their own InputMethod. - public static Imm32InputMethod Current { get; } = new Imm32InputMethod(); + public static Imm32InputMethod Current { get; } = new Imm32InputMethod(); - private IntPtr DefaultImc + public void Reset() { - get + Dispatcher.UIThread.Post(() => { - if (_defaultImc == IntPtr.Zero && - HWND != IntPtr.Zero) - { - _defaultImc = ImmGetContext(HWND); - ImmReleaseContext(HWND, _defaultImc); - } + var himc = ImmGetContext(HWND); - if (_defaultImc == IntPtr.Zero) + if (IsComposing) { - _defaultImc = ImmCreateContext(); + ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); + + IsComposing = false; } - return _defaultImc; - } - } - - public void Reset() - { - if (IsComposing) - { - Dispatcher.UIThread.Post(() => - { - ImmNotifyIME(DefaultImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - ImmReleaseContext(HWND, DefaultImc); - IsComposing = false; - }); - } + ImmReleaseContext(HWND, himc); + }); } public void SetClient(ITextInputMethodClient client) { - _active = client is { }; + _client = client; + Dispatcher.UIThread.Post(() => { - if (_active) + if (IsActive) { - if (DefaultImc != IntPtr.Zero) - { - _caretManager.TryCreate(_langId, HWND); - // Load the default IME context. - // NOTE(hbono) - // IMM ignores this call if the IME context is loaded. Therefore, we do - // not have to check whether or not the IME context is loaded. - ImmAssociateContext(HWND, _defaultImc); - } + EnableImm(); } else { @@ -116,14 +141,8 @@ namespace Avalonia.Win32.Input // mouse button and selected a password input while composing a text. // For this case, we have to complete the ongoing composition and // clean up the resources attached to this object BEFORE DISABLING THE IME. - if (IsComposing) - { - ImmNotifyIME(DefaultImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - ImmReleaseContext(HWND, DefaultImc); - IsComposing = false; - } - ImmAssociateContext(HWND, IntPtr.Zero); - _caretManager.TryDestroy(); + + DisableImm(); } }); } @@ -131,19 +150,23 @@ namespace Avalonia.Win32.Input public void SetCursorRect(Rect rect) { var focused = GetActiveWindow() == HWND; + if (!focused) { return; } + Dispatcher.UIThread.Post(() => { - IntPtr himc = DefaultImc; + var himc = ImmGetContext(HWND); + if (himc == IntPtr.Zero) { return; } MoveImeWindow(rect, himc); + ImmReleaseContext(HWND, himc); }); } @@ -157,8 +180,7 @@ namespace Avalonia.Win32.Input var s = _parent?.DesktopScaling ?? 1; var (x1, y1, x2, y2) = ((int) (p1.X * s), (int) (p1.Y * s), (int) (p2.X * s), (int) (p2.Y * s)); - if (!_showCompositionWindow && - _langId == LANG_ZH) + if (!ShowCompositionWindow && _langId == LANG_ZH) { // Chinese IMEs ignore function calls to ::ImmSetCandidateWindow() // when a user disables TSF (Text Service Framework) and CUAS (Cicero @@ -175,12 +197,13 @@ namespace Avalonia.Win32.Input dwStyle = CFS_CANDIDATEPOS, ptCurrentPos = new POINT {X = x2, Y = y2} }; + ImmSetCandidateWindow(himc, ref candidateForm); } - + _caretManager.TryMove(x2, y2); - if (_showCompositionWindow) + if (ShowCompositionWindow) { ConfigureCompositionWindow(x1, y1, himc, y2 - y1); // Don't need to set the position of candidate window. @@ -214,6 +237,7 @@ namespace Avalonia.Win32.Input ptCurrentPos = new POINT {X = x1, Y = y1}, rcArea = new RECT {left = x1, top = y1, right = x2, bottom = y2 + _caretMargin} }; + ImmSetCandidateWindow(himc, ref excludeRectangle); } @@ -224,6 +248,7 @@ namespace Avalonia.Win32.Input dwStyle = CFS_POINT, ptCurrentPos = new POINT {X = x1, Y = y1}, }; + ImmSetCompositionWindow(himc, ref compForm); var logFont = new LOGFONT() @@ -231,6 +256,7 @@ namespace Avalonia.Win32.Input lfHeight = height, lfQuality = 5 //CLEARTYPE_QUALITY }; + ImmSetCompositionFont(himc, ref logFont); } @@ -238,8 +264,43 @@ namespace Avalonia.Win32.Input { // we're skipping this. not usable on windows } + + public void CompositionChanged() + { + if (!IsComposing) + { + return; + } + + if(!IsActive || !_client.SupportsPreedit) + { + return; + } + + var composition = GetCompositionString(); + + _client.SetPreeditText(composition); + } - public bool IsComposing { get; set; } + private string GetCompositionString() + { + var himc = ImmGetContext(HWND); + + var length = ImmGetCompositionString(himc, GCS.GCS_COMPSTR, IntPtr.Zero, 0); + + var buffer = new byte[length]; + + unsafe + { + fixed (byte* bufferPtr = buffer) + { + var error = ImmGetCompositionString(himc, GCS.GCS_COMPSTR, (IntPtr)bufferPtr, (uint)length); + + return Encoding.Unicode.GetString(buffer, 0, buffer.Length); + } + } + + } ~Imm32InputMethod() { diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index ea01d5cbdf..9d1920498b 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -1766,6 +1766,46 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll")] internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); + [Flags] + public enum GCS : uint + { + /// Retrieve or update the attribute of the composition string. + GCS_COMPATTR = 0x0010, + + /// Retrieve or update clause information of the composition string. + GCS_COMPCLAUSE = 0x0020, + + /// Retrieve or update the attributes of the reading string of the current composition. + GCS_COMPREADATTR = 0x0002, + + /// Retrieve or update the clause information of the reading string of the composition string. + GCS_COMPREADCLAUSE = 0x0004, + + /// Retrieve or update the reading string of the current composition. + GCS_COMPREADSTR = 0x0001, + + /// Retrieve or update the current composition string. + GCS_COMPSTR = 0x0008, + + /// Retrieve or update the cursor position in composition string. + GCS_CURSORPOS = 0x0080, + + /// Retrieve or update the starting position of any changes in composition string. + GCS_DELTASTART = 0x0100, + + /// Retrieve or update clause information of the result string. + GCS_RESULTCLAUSE = 0x1000, + + /// Retrieve or update clause information of the reading string. + GCS_RESULTREADCLAUSE = 0x0400, + + /// Retrieve or update the reading string. + GCS_RESULTREADSTR = 0x0200, + + /// Retrieve or update the string of the composition result. + GCS_RESULTSTR = 0x0800, + } + [DllImport("imm32.dll", SetLastError = true)] public static extern IntPtr ImmGetContext(IntPtr hWnd); [DllImport("imm32.dll", SetLastError = true)] @@ -1788,6 +1828,29 @@ namespace Avalonia.Win32.Interop public static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM lpComp); [DllImport("imm32.dll")] public static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT lf); + + [DllImport("imm32.dll", SetLastError = false, CharSet = CharSet.Unicode)] + public static extern int ImmGetCompositionString(IntPtr hIMC, GCS dwIndex, [Out, Optional] IntPtr lpBuf, uint dwBufLen); + + public static string ImmGetCompositionString(IntPtr hIMC, GCS dwIndex) + { + int bufferLength = ImmGetCompositionString(hIMC, dwIndex, IntPtr.Zero, 0); + + if (bufferLength > 0) + { + var buffer = new byte[bufferLength]; + + fixed(byte* bufferPtr = buffer) + { + var error = ImmGetCompositionString(hIMC, dwIndex, (IntPtr)bufferPtr, (uint)bufferLength); + + return Marshal.PtrToStringUni((IntPtr)bufferPtr); + } + } + + return null; + } + [DllImport("imm32.dll")] public static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue); [DllImport("user32.dll")] @@ -1827,7 +1890,13 @@ namespace Avalonia.Win32.Interop public const int CFS_EXCLUDE = 0x0080; public const int CFS_POINT = 0x0002; public const int CFS_RECT = 0x0001; - public const uint ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000; + + // lParam for WM_IME_SETCONTEXT + public const long ISC_SHOWUICANDIDATEWINDOW = 0x00000001; + public const long ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000; + public const long ISC_SHOWUIGUIDELINE = 0x40000000; + public const long ISC_SHOWUIALLCANDIDATEWINDOW = 0x0000000F; + public const long ISC_SHOWUIALL = 0xC000000F; public const int NI_COMPOSITIONSTR = 21; public const int CPS_COMPLETE = 1; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 4c7b9a0348..9022bc4179 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -527,22 +527,22 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_PAINT: - { - using(NonPumpingSyncContext.Use()) - using (_rendererLock.Lock()) { - if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) + using (NonPumpingSyncContext.Use()) + using (_rendererLock.Lock()) { - var f = RenderScaling; - var r = ps.rcPaint; - Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, - (r.bottom - r.top) / f)); - EndPaint(_hwnd, ref ps); + if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) + { + var f = RenderScaling; + var r = ps.rcPaint; + Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, + (r.bottom - r.top) / f)); + EndPaint(_hwnd, ref ps); + } } - } - return IntPtr.Zero; - } + return IntPtr.Zero; + } case WindowsMessage.WM_ENTERSIZEMOVE: @@ -551,7 +551,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_SIZE: { - using(NonPumpingSyncContext.Use()) + using (NonPumpingSyncContext.Use()) using (_rendererLock.Lock()) { // Do nothing here, just block until the pending frame render is completed on the render thread @@ -651,13 +651,19 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_IME_SETCONTEXT: { - // TODO if we implement preedit, disable the composition window: - // lParam = new IntPtr((int)(((uint)lParam.ToInt64()) & ~ISC_SHOWUICOMPOSITIONWINDOW)); + DefWindowProc(Hwnd, msg, wParam, (IntPtr)(lParam.ToInt64() & ~ISC_SHOWUICOMPOSITIONWINDOW)); + UpdateInputMethod(GetKeyboardLayout(0)); + + return IntPtr.Zero; + } + case WindowsMessage.WM_IME_COMPOSITION: + { + Imm32InputMethod.Current.CompositionChanged(); + break; } case WindowsMessage.WM_IME_CHAR: - case WindowsMessage.WM_IME_COMPOSITION: case WindowsMessage.WM_IME_COMPOSITIONFULL: case WindowsMessage.WM_IME_CONTROL: case WindowsMessage.WM_IME_KEYDOWN: @@ -667,6 +673,7 @@ namespace Avalonia.Win32 break; case WindowsMessage.WM_IME_STARTCOMPOSITION: Imm32InputMethod.Current.IsComposing = true; + return IntPtr.Zero; break; case WindowsMessage.WM_IME_ENDCOMPOSITION: Imm32InputMethod.Current.IsComposing = false; @@ -687,7 +694,7 @@ namespace Avalonia.Win32 return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); #endif - if(shouldTakeFocus) + if (shouldTakeFocus) { SetFocus(_hwnd); } @@ -916,14 +923,15 @@ namespace Avalonia.Win32 { // note: for non-ime language, also create it so that emoji panel tracks cursor var langid = LGID(hkl); + if (langid == _langid && Imm32InputMethod.Current.HWND == Hwnd) { return; } + _langid = langid; Imm32InputMethod.Current.SetLanguageAndWindow(this, Hwnd, hkl); - } private static int ToInt32(IntPtr ptr) From 63539252a3cfd5152f1e89aaf05995f4d76c4342 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 9 Aug 2022 06:29:37 +0200 Subject: [PATCH 042/453] Cleanup --- samples/Sandbox/MainWindow.axaml | 6 +----- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index 20d7a53a11..43d93a9315 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,9 +1,5 @@ - - - - - + diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 9022bc4179..f8785371d9 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -674,7 +674,6 @@ namespace Avalonia.Win32 case WindowsMessage.WM_IME_STARTCOMPOSITION: Imm32InputMethod.Current.IsComposing = true; return IntPtr.Zero; - break; case WindowsMessage.WM_IME_ENDCOMPOSITION: Imm32InputMethod.Current.IsComposing = false; break; From 26e401a36cf3c14897c182a849b3cc79854041ea Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 9 Aug 2022 02:41:50 -0400 Subject: [PATCH 043/453] Revert ScrollGestureRecognizer changes for now --- .../Input/GestureRecognizers/ScrollGestureRecognizer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index 0fb991eaac..889b7e3b82 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -66,7 +66,8 @@ namespace Avalonia.Input.GestureRecognizers public void PointerPressed(PointerPressedEventArgs e) { - if (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen) + if (e.Pointer.IsPrimary && + (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { EndGesture(); _tracking = e.Pointer; From f314abd8abd719e014370f77d7f2cf1608231b73 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 9 Aug 2022 03:19:05 -0400 Subject: [PATCH 044/453] Fix android options initalization order --- src/Android/Avalonia.Android/AndroidPlatform.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 89444dea10..ed5b46d398 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -17,10 +17,8 @@ namespace Avalonia { public static T UseAndroid(this T builder) where T : AppBuilderBase, new() { - var options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); - return builder - .UseWindowingSubsystem(() => AndroidPlatform.Initialize(options), "Android") + .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android") .UseSkia(); } } @@ -45,9 +43,9 @@ namespace Avalonia.Android internal static Compositor Compositor { get; private set; } - public static void Initialize(AndroidPlatformOptions options) + public static void Initialize() { - Options = options; + Options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions(); AvaloniaLocator.CurrentMutable .Bind().ToTransient() @@ -61,12 +59,12 @@ namespace Avalonia.Android .Bind().ToConstant(new RenderLoop()) .Bind().ToSingleton(); - if (options.UseGpu) + if (Options.UseGpu) { EglPlatformOpenGlInterface.TryInitialize(); } - if (options.UseCompositor) + if (Options.UseCompositor) { Compositor = new Compositor( AvaloniaLocator.Current.GetRequiredService(), From a88451c650d4f3d130d9c743a4ac19725c72f122 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 9 Aug 2022 03:19:15 -0400 Subject: [PATCH 045/453] Reset render target when it's corrupted --- .../Composition/Server/ServerCompositionTarget.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index c20594aaca..5c1ac0312c 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -78,6 +78,13 @@ namespace Avalonia.Rendering.Composition.Server if (Root == null) return; + + if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) + { + _renderTarget!.Dispose(); + _renderTarget = null; + } + _renderTarget ??= _renderTargetFactory(); Compositor.UpdateServerTime(); From 2c39f80f9bab22fbdb66d41db33543a84f556ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 9 Aug 2022 17:43:12 +0100 Subject: [PATCH 046/453] Changed Skia OpenGL bitmap origin to bottom left. --- samples/ControlCatalog/Pages/OpenGlPage.xaml.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs index f37e0f8701..063ad50b0b 100644 --- a/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs +++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml.cs @@ -345,7 +345,7 @@ namespace ControlCatalog.Pages 0.01f, 1000); - var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0)); + var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, 1, 0)); var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll); var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel"); var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView"); diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index a4617bb4d5..fb69a1ad3d 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -41,7 +41,7 @@ namespace Avalonia.Skia new GRGlTextureInfo( GlConsts.GL_TEXTURE_2D, (uint)_surface.GetTextureId(), (uint)_surface.InternalFormat))) - using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.TopLeft, + using (var surface = SKSurface.Create(context.GrContext, backendTexture, GRSurfaceOrigin.BottomLeft, SKColorType.Rgba8888)) { // Again, silently ignore, if something went wrong it's not our fault From 6054fa98f117d3b64adf70ae2c09767a0662af3b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 9 Aug 2022 17:48:44 +0100 Subject: [PATCH 047/453] fix reliability issue in osx window state test. --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 2b10c302bc..5afcb7cf43 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -92,7 +92,8 @@ namespace Avalonia.IntegrationTests.Appium Assert.True(clientSize.Width >= current.ScreenRect.Width); Assert.True(clientSize.Height >= current.ScreenRect.Height); - windowState.Click(); + windowState.SendClick(); + _session.FindElementByName("Normal").SendClick(); current = GetWindowInfo(); From 3e3661a2d423ac40b020f697698403c8e384b795 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 9 Aug 2022 18:00:10 +0100 Subject: [PATCH 048/453] Removed outdated comment in PR template. --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 46e8665945..2f63750cdc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,7 +21,7 @@ - [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation ## Breaking changes - + ## Obsoletions / Deprecations From f63ed9cf6bf19b8490c55d2f9bf6cc6b9ae05fb3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 10 Aug 2022 10:33:00 +0200 Subject: [PATCH 049/453] fix: Null Annotation --- samples/ControlCatalog/MainView.xaml.cs | 6 +- samples/ControlCatalog/Models/Person.cs | 8 +-- .../Pages/AutoCompleteBoxPage.xaml.cs | 24 +++---- .../Pages/ButtonSpinnerPage.xaml.cs | 25 ++++--- .../ControlCatalog/Pages/ButtonsPage.xaml.cs | 2 +- .../ControlCatalog/Pages/CarouselPage.xaml.cs | 2 +- .../Pages/ClipboardPage.xaml.cs | 70 +++++++++++++------ .../ControlCatalog/Pages/ComboBoxPage.xaml.cs | 2 +- .../Pages/CompositionPage.axaml.cs | 20 +++--- .../Pages/ContextFlyoutPage.xaml.cs | 22 +++--- .../Pages/ContextMenuPage.xaml.cs | 23 +++--- .../ControlCatalog/Pages/DataGridPage.xaml.cs | 4 +- .../ControlCatalog/Pages/DialogsPage.xaml.cs | 13 +++- .../Pages/DragAndDropPage.xaml.cs | 20 +++--- .../ControlCatalog/Pages/FlyoutsPage.axaml.cs | 2 +- .../Pages/ItemsRepeaterPage.xaml.cs | 10 +-- .../Pages/NumericUpDownPage.xaml.cs | 6 +- .../ControlCatalog/Pages/OpenGlPage.xaml.cs | 4 +- .../ControlCatalog/Pages/PointersPage.xaml.cs | 31 ++++---- ...ransitioningContentControlPageViewModel.cs | 6 +- 20 files changed, 165 insertions(+), 135 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 58433f13ce..7133ddaa6a 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -22,8 +22,8 @@ namespace ControlCatalog if (AvaloniaLocator.Current?.GetService()?.GetRuntimeInfo().IsDesktop == true) { - IList tabItems = ((IList)sideBar.Items); - tabItems.Add(new TabItem() + var tabItems = (sideBar.Items as IList); + tabItems?.Add(new TabItem() { Header = "Screens", Content = new ScreenPage() @@ -36,7 +36,7 @@ namespace ControlCatalog { if (themes.SelectedItem is CatalogTheme theme) { - var themeStyle = Application.Current.Styles[0]; + var themeStyle = Application.Current!.Styles[0]; if (theme == CatalogTheme.FluentLight) { if (App.Fluent.Mode != FluentThemeMode.Light) diff --git a/samples/ControlCatalog/Models/Person.cs b/samples/ControlCatalog/Models/Person.cs index 2dfa02c7ed..99bc50250b 100644 --- a/samples/ControlCatalog/Models/Person.cs +++ b/samples/ControlCatalog/Models/Person.cs @@ -85,7 +85,7 @@ namespace ControlCatalog.Models } else { - if (_errorLookup.TryGetValue(propertyName, out List errorList)) + if (_errorLookup.TryGetValue(propertyName, out var errorList)) { errorList.Clear(); errorList.Add(error!); @@ -114,12 +114,12 @@ namespace ControlCatalog.Models PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - public IEnumerable? GetErrors(string propertyName) + public IEnumerable GetErrors(string? propertyName) { - if (_errorLookup.TryGetValue(propertyName, out List errorList)) + if (propertyName is { } && _errorLookup.TryGetValue(propertyName, out var errorList)) return errorList; else - return null; + return Array.Empty(); } } } diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs index 7a0957b02d..bc18327f12 100644 --- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs @@ -1,8 +1,6 @@ using Avalonia.Controls; using Avalonia.LogicalTree; -using Avalonia.Markup; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Data; using System; using System.Collections.Generic; using System.Linq; @@ -161,23 +159,23 @@ namespace ControlCatalog.Pages private bool LastWordContains(string? searchText, string? item) { var words = searchText?.Split(' ') ?? Array.Empty(); - var options = Sentences.Select(x => x.First).ToArray(); + var options = Sentences.Select(x => x.First) + .ToArray?>(); for (var i = 0; i < words.Length; ++i) { var word = words[i]; for (var j = 0; word is { } && j < options.Length; ++j) { - var option = options[j]; - if (option == null) - continue; - - if (i == words.Length - 1) - { - options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; - } - else + if (options[i] is { } option) { - options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + if (i == words.Length - 1) + { + options[j] = option.Value.ToLower().Contains(word.ToLower()) ? option : null; + } + else + { + options[j] = option.Value.Equals(word, StringComparison.InvariantCultureIgnoreCase) ? option.Next : null; + } } } } diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs index 5c584b8781..e7450075ad 100644 --- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs @@ -21,20 +21,23 @@ namespace ControlCatalog.Pages public void OnSpin(object sender, SpinEventArgs e) { var spinner = (ButtonSpinner)sender; - var txtBox = (TextBlock)spinner.Content; - int value = Array.IndexOf(_mountains, txtBox?.Text); - if (e.Direction == SpinDirection.Increase) - value++; - else - value--; + if (spinner.Content is TextBlock txtBox) + { + int value = Array.IndexOf(_mountains, txtBox.Text); + if (e.Direction == SpinDirection.Increase) + value++; + else + value--; - if (value < 0) - value = _mountains.Length - 1; - else if (value >= _mountains.Length) - value = 0; + if (value < 0) + value = _mountains.Length - 1; + else if (value >= _mountains.Length) + value = 0; + + txtBox.Text = _mountains[value]; + } - txtBox.Text = _mountains[value]; } private readonly string[] _mountains = new[] diff --git a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs index 3e748dd6f6..2d63f1fee9 100644 --- a/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ButtonsPage.xaml.cs @@ -19,7 +19,7 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } - public void OnRepeatButtonClick(object sender, object args) + public void OnRepeatButtonClick(object? sender, object args) { repeatButtonClickCount++; var textBlock = this.Get("RepeatButtonTextBlock"); diff --git a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs index 5b74e3e19e..c6aab5c4d5 100644 --- a/samples/ControlCatalog/Pages/CarouselPage.xaml.cs +++ b/samples/ControlCatalog/Pages/CarouselPage.xaml.cs @@ -33,7 +33,7 @@ namespace ControlCatalog.Pages } - private void TransitionChanged(object sender, SelectionChangedEventArgs e) + private void TransitionChanged(object? sender, SelectionChangedEventArgs e) { switch (_transition.SelectedIndex) { diff --git a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs index eed46265ff..ef3d2bbafa 100644 --- a/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ClipboardPage.xaml.cs @@ -23,55 +23,79 @@ namespace ControlCatalog.Pages AvaloniaXamlLoader.Load(this); } - private async void CopyText(object sender, RoutedEventArgs args) + private async void CopyText(object? sender, RoutedEventArgs args) { - await Application.Current.Clipboard.SetTextAsync(ClipboardContent.Text); + if (Application.Current!.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent) + await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty); } - private async void PasteText(object sender, RoutedEventArgs args) + private async void PasteText(object? sender, RoutedEventArgs args) { - ClipboardContent.Text = await Application.Current.Clipboard.GetTextAsync(); + if(Application.Current!.Clipboard is { } clipboard) + { + ClipboardContent.Text = await clipboard.GetTextAsync(); + } } - private async void CopyTextDataObject(object sender, RoutedEventArgs args) + private async void CopyTextDataObject(object? sender, RoutedEventArgs args) { - var dataObject = new DataObject(); - dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); - await Application.Current.Clipboard.SetDataObjectAsync(dataObject); + if (Application.Current!.Clipboard is { } clipboard) + { + var dataObject = new DataObject(); + dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty); + await clipboard.SetDataObjectAsync(dataObject); + } } - private async void PasteTextDataObject(object sender, RoutedEventArgs args) + private async void PasteTextDataObject(object? sender, RoutedEventArgs args) { - ClipboardContent.Text = await Application.Current.Clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; + if (Application.Current!.Clipboard is { } clipboard) + { + ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty; + } } - private async void CopyFilesDataObject(object sender, RoutedEventArgs args) + private async void CopyFilesDataObject(object? sender, RoutedEventArgs args) { - var files = ClipboardContent.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); - if (files.Length == 0) + if (Application.Current!.Clipboard is { } clipboard) { - return; + var files = (ClipboardContent.Text ?? String.Empty) + .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + if (files.Length == 0) + { + return; + } + var dataObject = new DataObject(); + dataObject.Set(DataFormats.FileNames, files); + await clipboard.SetDataObjectAsync(dataObject); } - var dataObject = new DataObject(); - dataObject.Set(DataFormats.FileNames, files); - await Application.Current.Clipboard.SetDataObjectAsync(dataObject); } - private async void PasteFilesDataObject(object sender, RoutedEventArgs args) + private async void PasteFilesDataObject(object? sender, RoutedEventArgs args) { - var fiels = await Application.Current.Clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable; - ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; + if (Application.Current!.Clipboard is { } clipboard) + { + var fiels = await clipboard.GetDataAsync(DataFormats.FileNames) as IEnumerable; + ClipboardContent.Text = fiels != null ? string.Join(Environment.NewLine, fiels) : string.Empty; + } } private async void GetFormats(object sender, RoutedEventArgs args) { - var formats = await Application.Current.Clipboard.GetFormatsAsync(); - ClipboardContent.Text = string.Join(Environment.NewLine, formats); + if (Application.Current!.Clipboard is { } clipboard) + { + var formats = await clipboard.GetFormatsAsync(); + ClipboardContent.Text = string.Join(Environment.NewLine, formats); + } } private async void Clear(object sender, RoutedEventArgs args) { - await Application.Current.Clipboard.ClearAsync(); + if (Application.Current!.Clipboard is { } clipboard) + { + await clipboard.ClearAsync(); + } + } } } diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs index d304bf227d..6d624c9a07 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml.cs @@ -17,7 +17,7 @@ namespace ControlCatalog.Pages private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - var fontComboBox = this.Find("fontComboBox"); + var fontComboBox = this.Get("fontComboBox"); fontComboBox.Items = FontManager.Current.GetInstalledFontFamilyNames().Select(x => new FontFamily(x)); fontComboBox.SelectedIndex = 0; } diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs index 18069ca857..61e0ed5acb 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml.cs +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml.cs @@ -1,14 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Interactivity; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.Templates; using Avalonia.Media; using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition.Animations; @@ -18,7 +12,7 @@ namespace ControlCatalog.Pages; public partial class CompositionPage : UserControl { - private ImplicitAnimationCollection _implicitAnimations; + private ImplicitAnimationCollection? _implicitAnimations; public CompositionPage() { @@ -28,7 +22,7 @@ public partial class CompositionPage : UserControl protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); - this.FindControl("Items").Items = CreateColorItems(); + this.Get("Items").Items = CreateColorItems(); } private List CreateColorItems() @@ -115,7 +109,6 @@ public partial class CompositionPage : UserControl public static void SetEnableAnimations(Border border, bool value) { - var page = border.FindAncestorOfType(); if (page == null) { @@ -127,8 +120,11 @@ public partial class CompositionPage : UserControl return; page.EnsureImplicitAnimations(); - ElementComposition.GetElementVisual((Visual)border.GetVisualParent()).ImplicitAnimations = - page._implicitAnimations; + if (border.GetVisualParent() is Visual visualParent + && ElementComposition.GetElementVisual(visualParent) is CompositionVisual compositionVisual) + { + compositionVisual.ImplicitAnimations = page._implicitAnimations; + } } } @@ -150,4 +146,4 @@ public class CompositionPageColorItem { Color = color; } -} \ No newline at end of file +} diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs index 4d72fc5311..8bd1f4d85a 100644 --- a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml.cs @@ -52,13 +52,13 @@ namespace ControlCatalog.Pages base.OnDataContextChanged(e); } - private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) + private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e) { var cancelCloseCheckBox = this.FindControl("CancelCloseCheckBox"); e.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } - private void ContextFlyoutPage_Opening(object sender, EventArgs e) + private void ContextFlyoutPage_Opening(object? sender, EventArgs e) { if (e is CancelEventArgs cancelArgs) { @@ -67,20 +67,20 @@ namespace ControlCatalog.Pages } } - private void CloseFlyout(object sender, RoutedEventArgs e) + private void CloseFlyout(object? sender, RoutedEventArgs e) { _textBox.ContextFlyout?.Hide(); } - public void CustomContextRequested(object sender, ContextRequestedEventArgs e) + public void CustomContextRequested(object? sender, ContextRequestedEventArgs e) { - var border = (Border)sender; - var textBlock = (TextBlock)border.Child; - - textBlock.Text = e.TryGetPosition(border, out var point) - ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" - : "Context was requested without pointer"; - e.Handled = true; + if (sender is Border border && border.Child is TextBlock textBlock) + { + textBlock.Text = e.TryGetPosition(border, out var point) + ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" + : "Context was requested without pointer"; + e.Handled = true; + } } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index 4581642024..96fcb54650 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -35,30 +35,31 @@ namespace ControlCatalog.Pages base.OnDataContextChanged(e); } - private void ContextFlyoutPage_Closing(object sender, CancelEventArgs e) + private void ContextFlyoutPage_Closing(object? sender, CancelEventArgs e) { var cancelCloseCheckBox = this.FindControl("CancelCloseCheckBox"); - e.Cancel = cancelCloseCheckBox.IsChecked ?? false; + e.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } - private void ContextFlyoutPage_Opening(object sender, EventArgs e) + private void ContextFlyoutPage_Opening(object? sender, EventArgs e) { if (e is CancelEventArgs cancelArgs) { var cancelCloseCheckBox = this.FindControl("CancelOpenCheckBox"); - cancelArgs.Cancel = cancelCloseCheckBox.IsChecked ?? false; + cancelArgs.Cancel = cancelCloseCheckBox?.IsChecked ?? false; } } - public void CustomContextRequested(object sender, ContextRequestedEventArgs e) + public void CustomContextRequested(object? sender, ContextRequestedEventArgs e) { - var border = (Border)sender; - var textBlock = (TextBlock)border.Child; + if (sender is Border border && border.Child is TextBlock textBlock) + { + textBlock.Text = e.TryGetPosition(border, out var point) + ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" + : "Context was requested without pointer"; + e.Handled = true; + } - textBlock.Text = e.TryGetPosition(border, out var point) - ? $"Context was requested with pointer at: {point.X:N0}, {point.Y:N0}" - : "Context was requested without pointer"; - e.Handled = true; } private void InitializeComponent() diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs index 219b7aeac4..3565d113bc 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml.cs @@ -62,7 +62,7 @@ namespace ControlCatalog.Pages addButton.Click += (a, b) => collectionView3.AddNew(); } - private void Dg1_LoadingRow(object sender, DataGridRowEventArgs e) + private void Dg1_LoadingRow(object? sender, DataGridRowEventArgs e) { e.Row.Header = e.Row.GetIndex() + 1; } @@ -74,7 +74,7 @@ namespace ControlCatalog.Pages private class ReversedStringComparer : IComparer, IComparer { - public int Compare(object x, object y) + public int Compare(object? x, object? y) { if (x is string left && y is string right) { diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 036dccde0e..67e9ef4e40 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -111,9 +111,16 @@ namespace ControlCatalog.Pages Title = "Select folder", Directory = lastSelectedDirectory?.TryGetUri(out var path) == true ? path.LocalPath : null }.ShowAsync(GetWindow()); - lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result)); - results.Items = new [] { result }; - resultsVisible.IsVisible = result != null; + if (string.IsNullOrEmpty(result)) + { + resultsVisible.IsVisible = false; + } + else + { + lastSelectedDirectory = new BclStorageFolder(new System.IO.DirectoryInfo(result)); + results.Items = new[] { result }; + resultsVisible.IsVisible = true; + } }; this.Get