From 1f06e4af50ae9ff56d4af8869cf0cdf0298e98ba Mon Sep 17 00:00:00 2001 From: Vitalii Orazov Date: Sun, 2 Jul 2023 13:31:33 +0300 Subject: [PATCH 01/59] use real data formats while storing atoms early --- src/Avalonia.X11/X11Clipboard.cs | 37 +++++++++++++++++++------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index 6e18151fae..bed0a8cebf 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -82,18 +82,9 @@ namespace Avalonia.X11 Encoding textEnc; if (target == _x11.Atoms.TARGETS) { - var atoms = new HashSet { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE }; - foreach (var fmt in _storedDataObject.GetDataFormats()) - { - if (fmt == DataFormats.Text) - foreach (var ta in _textAtoms) - atoms.Add(ta); - else - atoms.Add(_x11.Atoms.GetAtom(fmt)); - } - + var atoms = ConvertDataObject(_storedDataObject); XChangeProperty(_x11.Display, window, property, - _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms.ToArray(), atoms.Count); + _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length); return property; } else if(target == _x11.Atoms.SAVE_TARGETS && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) @@ -252,13 +243,29 @@ namespace Avalonia.X11 return (string)await SendDataRequest(target); } - private void StoreAtomsInClipboardManager(IntPtr[] atoms) + + private IntPtr[] ConvertDataObject(IDataObject data) + { + var atoms = new HashSet { _x11.Atoms.TARGETS, _x11.Atoms.MULTIPLE }; + foreach (var fmt in data.GetDataFormats()) + { + if (fmt == DataFormats.Text) + foreach (var ta in _textAtoms) + atoms.Add(ta); + else + atoms.Add(_x11.Atoms.GetAtom(fmt)); + } + return atoms.ToArray(); + } + + private void StoreAtomsInClipboardManager(IDataObject data) { if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) { var clipboardManager = XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER); if (clipboardManager != IntPtr.Zero) - { + { + var atoms = ConvertDataObject(data); XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length); @@ -283,8 +290,8 @@ namespace Avalonia.X11 public Task SetDataObjectAsync(IDataObject data) { _storedDataObject = data; - XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); - StoreAtomsInClipboardManager(_textAtoms); + XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); + StoreAtomsInClipboardManager(data); return Task.CompletedTask; } From 0d1f7fac56936312c2d4249eb182ea6ae137dd35 Mon Sep 17 00:00:00 2001 From: Vitalii Orazov Date: Sun, 2 Jul 2023 14:19:10 +0300 Subject: [PATCH 02/59] await for completion when storing atoms early --- src/Avalonia.X11/X11Clipboard.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index bed0a8cebf..53190b0f3e 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -14,6 +14,7 @@ namespace Avalonia.X11 private readonly X11Info _x11; private IDataObject _storedDataObject; private IntPtr _handle; + private TaskCompletionSource _storeAtomTcs; private TaskCompletionSource _requestedFormatsTcs; private TaskCompletionSource _requestedDataTcs; private readonly IntPtr[] _textAtoms; @@ -52,6 +53,12 @@ namespace Avalonia.X11 private unsafe void OnEvent(ref XEvent ev) { + if (ev.type == XEventName.SelectionClear) + { + _storeAtomTcs?.TrySetResult(); + return; + } + if (ev.type == XEventName.SelectionRequest) { var sel = ev.SelectionRequestEvent; @@ -258,21 +265,26 @@ namespace Avalonia.X11 return atoms.ToArray(); } - private void StoreAtomsInClipboardManager(IDataObject data) + private Task StoreAtomsInClipboardManager(IDataObject data) { if (_x11.Atoms.CLIPBOARD_MANAGER != IntPtr.Zero && _x11.Atoms.SAVE_TARGETS != IntPtr.Zero) { var clipboardManager = XGetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER); if (clipboardManager != IntPtr.Zero) - { + { + if (_storeAtomTcs == null || _storeAtomTcs.Task.IsCompleted) + _storeAtomTcs = new TaskCompletionSource(); + var atoms = ConvertDataObject(data); XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32, PropertyMode.Replace, atoms, atoms.Length); XConvertSelection(_x11.Display, _x11.Atoms.CLIPBOARD_MANAGER, _x11.Atoms.SAVE_TARGETS, _avaloniaSaveTargetsAtom, _handle, IntPtr.Zero); + return _storeAtomTcs.Task; } } + return Task.CompletedTask; } public Task SetTextAsync(string text) @@ -291,8 +303,7 @@ namespace Avalonia.X11 { _storedDataObject = data; XSetSelectionOwner(_x11.Display, _x11.Atoms.CLIPBOARD, _handle, IntPtr.Zero); - StoreAtomsInClipboardManager(data); - return Task.CompletedTask; + return StoreAtomsInClipboardManager(data); } public async Task GetFormatsAsync() From 22f68362b6af22a1ebdb39373a2aa142dd468f1c Mon Sep 17 00:00:00 2001 From: Vitalii Orazov Date: Sun, 2 Jul 2023 19:47:19 +0300 Subject: [PATCH 03/59] fix error CS0305: Using the generic type 'TaskCompletionSource' requires 1 type arguments --- src/Avalonia.X11/X11Clipboard.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.X11/X11Clipboard.cs b/src/Avalonia.X11/X11Clipboard.cs index 53190b0f3e..637d44d617 100644 --- a/src/Avalonia.X11/X11Clipboard.cs +++ b/src/Avalonia.X11/X11Clipboard.cs @@ -14,7 +14,7 @@ namespace Avalonia.X11 private readonly X11Info _x11; private IDataObject _storedDataObject; private IntPtr _handle; - private TaskCompletionSource _storeAtomTcs; + private TaskCompletionSource _storeAtomTcs; private TaskCompletionSource _requestedFormatsTcs; private TaskCompletionSource _requestedDataTcs; private readonly IntPtr[] _textAtoms; @@ -55,7 +55,7 @@ namespace Avalonia.X11 { if (ev.type == XEventName.SelectionClear) { - _storeAtomTcs?.TrySetResult(); + _storeAtomTcs?.TrySetResult(true); return; } @@ -273,7 +273,7 @@ namespace Avalonia.X11 if (clipboardManager != IntPtr.Zero) { if (_storeAtomTcs == null || _storeAtomTcs.Task.IsCompleted) - _storeAtomTcs = new TaskCompletionSource(); + _storeAtomTcs = new TaskCompletionSource(); var atoms = ConvertDataObject(data); XChangeProperty(_x11.Display, _handle, _avaloniaSaveTargetsAtom, _x11.Atoms.XA_ATOM, 32, From 0e038492f7e5e20c2d7ceb3d1e69a564fe148e27 Mon Sep 17 00:00:00 2001 From: Daniil Pavliuchyk Date: Fri, 7 Jul 2023 19:03:18 +0300 Subject: [PATCH 04/59] Do not set Window Startup Location multiple times --- samples/Sandbox/MainWindow.axaml | 8 +---- src/Avalonia.Controls/Window.cs | 7 ++++ .../WindowTests.cs | 35 +++++++++++++++++++ 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index f96abcac96..d8614983b7 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,11 +1,5 @@ - - - - - - - + diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ce4fc8dd5c..dc58947994 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -169,6 +169,7 @@ namespace Avalonia.Controls private readonly Size _maxPlatformClientSize; private bool _shown; private bool _showingAsDialog; + private bool _wasShownBefore; /// /// Initializes static members of the class. @@ -718,6 +719,7 @@ namespace Avalonia.Controls StartRendering(); PlatformImpl?.Show(ShowActivated, false); OnOpened(EventArgs.Empty); + _wasShownBefore = true; } } @@ -871,6 +873,11 @@ namespace Avalonia.Controls private void SetWindowStartupLocation(Window? owner = null) { + if (_wasShownBefore == true) + { + return; + } + var startupLocation = WindowStartupLocation; if (startupLocation == WindowStartupLocation.CenterOwner && diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index cd45a3d920..186b2f6836 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -513,6 +513,41 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Window_Should_Not_Be_Centered_When_WindowStartupLocation_Is_CenterScreen_And_Window_Is_Hidden_And_Shown() + { + var screen1 = new Mock(1.0, new PixelRect(new PixelSize(1920, 1080)), new PixelRect(new PixelSize(1920, 1040)), true); + + var screens = new Mock(); + screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object }); + screens.Setup(x => x.ScreenFromPoint(It.IsAny())).Returns(screen1.Object); + + + var windowImpl = MockWindowingPlatform.CreateWindowMock(); + windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); + windowImpl.Setup(x => x.DesktopScaling).Returns(1); + windowImpl.Setup(x => x.RenderScaling).Returns(1); + windowImpl.Setup(x => x.Screen).Returns(screens.Object); + + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var window = new Window(windowImpl.Object) + { + WindowStartupLocation = WindowStartupLocation.CenterScreen + }; + + window.Show(); + + var expected = new PixelPoint(150, 400); + window.Position = expected; + + window.IsVisible = false; + window.IsVisible = true; + + Assert.Equal(expected, window.Position); + } + } + [Fact] public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen() { From c21e9ccbb4559caafceaf49795d002caf7907013 Mon Sep 17 00:00:00 2001 From: Daniil Pavliuchyk Date: Fri, 7 Jul 2023 19:03:47 +0300 Subject: [PATCH 05/59] Cleanup --- samples/Sandbox/MainWindow.axaml | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index d8614983b7..6929f192c7 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,5 +1,4 @@ - From 19bae967d30a00d2767921b1842087321dae624b Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Sun, 9 Jul 2023 18:09:48 -0400 Subject: [PATCH 06/59] Rename ImportCompeted into ImportCompleted --- .../Composition/CompositionExternalMemory.cs | 11 +++++++++-- .../Rendering/Composition/CompositionInterop.cs | 6 +++--- .../Server/ServerCompositionDrawingSurface.cs | 6 +++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs index ce728f86a2..476e15c47f 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs @@ -84,14 +84,21 @@ public enum CompositionGpuImportedImageSynchronizationCapabilities /// /// An imported GPU object that's usable by composition APIs /// +[NotClientImplementable] public interface ICompositionGpuImportedObject : IAsyncDisposable { /// /// Tracks the import status of the object. Once the task is completed, /// the user code is allowed to free the resource owner in case when a non-owning - /// sharing handle was used + /// sharing handle was used. /// - Task ImportCompeted { get; } + Task ImportCompleted { get; } + + /// + /// ImportCompleted (recommended replacement) + [Obsolete("Please use ICompositionGpuImportedObject.ImportCompleted instead")] + Task ImportCompeted => ImportCompleted; + /// /// Indicates if the device context this instance is associated with is no longer available /// diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs index 1643ec6e8d..9eea228e84 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs @@ -67,18 +67,18 @@ abstract class CompositionGpuImportedObjectBase : ICompositionGpuImportedObject Context = context; Feature = feature; - ImportCompeted = Compositor.InvokeServerJobAsync(Import); + ImportCompleted = Compositor.InvokeServerJobAsync(Import); } protected abstract void Import(); public abstract void Dispose(); - public Task ImportCompeted { get; } + public Task ImportCompleted { get; } public bool IsLost => Context.IsLost; public ValueTask DisposeAsync() => new(Compositor.InvokeServerJobAsync(() => { - if (ImportCompeted.Status == TaskStatus.RanToCompletion) + if (ImportCompleted.Status == TaskStatus.RanToCompletion) Dispose(); })); } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs index da24b3812a..f58403f0bc 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawingSurface.cs @@ -31,12 +31,12 @@ internal class ServerCompositionDrawingSurface : ServerCompositionSurface, IDisp throw new PlatformGraphicsContextLostException(); // This should never happen, but check for it anyway to avoid a deadlock - if (!image.ImportCompeted.IsCompleted) + if (!image.ImportCompleted.IsCompleted) throw new InvalidOperationException("The import operation is not completed yet"); // Rethrow the import here exception - if (image.ImportCompeted.IsFaulted) - image.ImportCompeted.GetAwaiter().GetResult(); + if (image.ImportCompleted.IsFaulted) + image.ImportCompleted.GetAwaiter().GetResult(); } void Update(IBitmapImpl newImage, IPlatformRenderInterfaceContext context) From 1e5975d4ded724c362e11e28894fef1d834ac8d3 Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Sun, 9 Jul 2023 22:04:58 -0400 Subject: [PATCH 07/59] Hide ImportCompeted from smart completion (to avoid confusion) --- .../Rendering/Composition/CompositionExternalMemory.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs index 476e15c47f..236cb89fcd 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Threading.Tasks; using Avalonia.Metadata; using Avalonia.Platform; @@ -97,6 +98,7 @@ public interface ICompositionGpuImportedObject : IAsyncDisposable /// /// ImportCompleted (recommended replacement) [Obsolete("Please use ICompositionGpuImportedObject.ImportCompleted instead")] + [EditorBrowsable(EditorBrowsableState.Never)] Task ImportCompeted => ImportCompleted; /// From 157c6e91a000d57e38692b9fca8383529c769619 Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Fri, 9 Jun 2023 21:38:32 +0200 Subject: [PATCH 08/59] Add an overload for GetObservable that accepts an untyped AvaloniaProperty and a converter to a typed value Added missing docstrings for type params --- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index b3f41eb420..833cd4b034 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -60,6 +60,8 @@ namespace Avalonia } /// + /// The type of the values held by the . + /// The type of the value returned by the . /// /// /// A method which is executed to convert each property value to . @@ -71,6 +73,15 @@ namespace Avalonia converter ?? throw new ArgumentNullException(nameof(converter))); } + /// + public static IObservable GetObservable(this AvaloniaObject o, AvaloniaProperty property, Func converter) + { + return new AvaloniaPropertyObservable( + o ?? throw new ArgumentNullException(nameof(o)), + property ?? throw new ArgumentNullException(nameof(property)), + converter ?? throw new ArgumentNullException(nameof(converter))); + } + /// /// Gets an observable for an . /// @@ -92,6 +103,15 @@ namespace Avalonia property ?? throw new ArgumentNullException(nameof(property))); } + /// + public static IObservable> GetBindingObservable(this AvaloniaObject o, AvaloniaProperty property, Func converter) + { + return new AvaloniaPropertyBindingObservable( + o ?? throw new ArgumentNullException(nameof(o)), + property ?? throw new ArgumentNullException(nameof(property)), + converter?? throw new ArgumentNullException(nameof(converter))); + } + /// /// Gets an observable for an . /// From aa7a2a27644b974b06da0f4108c5a30d3c7368e9 Mon Sep 17 00:00:00 2001 From: startewho Date: Thu, 13 Jul 2023 17:11:07 +0800 Subject: [PATCH 09/59] fix 12161 --- src/Avalonia.Controls/Utils/StringUtils.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Utils/StringUtils.cs b/src/Avalonia.Controls/Utils/StringUtils.cs index 2ea47b442a..b664687650 100644 --- a/src/Avalonia.Controls/Utils/StringUtils.cs +++ b/src/Avalonia.Controls/Utils/StringUtils.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls.Utils } var codepoint = new Codepoint(text[index]); - + if (!codepoint.IsWhiteSpace) { return false; @@ -85,12 +85,16 @@ namespace Avalonia.Controls.Utils // preceeded by lwsp. if (index > 0) { - var nextCodePoint = new Codepoint(text[index + 1]); - - if (nextCodePoint.IsBreakChar) + if (index + 1 < text.Length) { - return true; + var nextCodePoint = new Codepoint(text[index + 1]); + + if (nextCodePoint.IsBreakChar) + { + return true; + } } + return true; } switch (codepoint.GeneralCategory) @@ -125,7 +129,7 @@ namespace Avalonia.Controls.Utils } cursor = Math.Min(cursor, text.Length); - + int begin; int i; int cr; @@ -181,7 +185,7 @@ namespace Avalonia.Controls.Utils { return cursor; } - + if (cr < text.Length && text[cr] == '\r' && cr + 1 < text.Length && text[cr + 1] == '\n') { lf = cr + 1; @@ -214,9 +218,9 @@ namespace Avalonia.Controls.Utils { return i; } - + var cc = GetCharClass(text[i]); - + // skip over the word, punctuation, or run of whitespace while (i < cr && GetCharClass(text[i]) == cc) { From 22ef68e43960f57368c593ca31841fe90c9f4f3a Mon Sep 17 00:00:00 2001 From: Gundlack Florian Date: Thu, 13 Jul 2023 13:45:16 +0200 Subject: [PATCH 10/59] Fixed issue where RowDetailsTemplate was getting the wrong DataContext --- src/Avalonia.Controls.DataGrid/DataGridRows.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 44079d24d0..107a2a7353 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -2970,10 +2970,7 @@ namespace Avalonia.Controls if (detailsContent != null) { _rowsPresenter.Children.Add(detailsContent); - if (dataItem != null) - { - detailsContent.DataContext = dataItem; - } + detailsContent.DataContext = dataItem; detailsContent.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); RowDetailsHeightEstimate = detailsContent.DesiredSize.Height; _rowsPresenter.Children.Remove(detailsContent); From 3fc0d19ad017a819535a0610058652aa07d57348 Mon Sep 17 00:00:00 2001 From: Gundlack Florian Date: Thu, 13 Jul 2023 15:47:28 +0200 Subject: [PATCH 11/59] Moved detailsContent.DataContext assignation before Children.Add --- src/Avalonia.Controls.DataGrid/DataGridRows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridRows.cs b/src/Avalonia.Controls.DataGrid/DataGridRows.cs index 107a2a7353..c9a348d172 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRows.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRows.cs @@ -2969,8 +2969,8 @@ namespace Avalonia.Controls var detailsContent = RowDetailsTemplate.Build(dataItem); if (detailsContent != null) { - _rowsPresenter.Children.Add(detailsContent); detailsContent.DataContext = dataItem; + _rowsPresenter.Children.Add(detailsContent); detailsContent.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); RowDetailsHeightEstimate = detailsContent.DesiredSize.Height; _rowsPresenter.Children.Remove(detailsContent); From e25995f3c94e2f8a9781c8d93afe3e9ee5d229cf Mon Sep 17 00:00:00 2001 From: jgcodes2020 Date: Thu, 13 Jul 2023 10:25:58 -0400 Subject: [PATCH 12/59] Implement @maxkatz6's suggestions --- .../Rendering/Composition/CompositionExternalMemory.cs | 4 +++- src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs index 236cb89fcd..31cbaf2a29 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionExternalMemory.cs @@ -6,6 +6,8 @@ using Avalonia.Metadata; using Avalonia.Platform; namespace Avalonia.Rendering.Composition; + +[NotClientImplementable] public interface ICompositionGpuInterop { /// @@ -99,7 +101,7 @@ public interface ICompositionGpuImportedObject : IAsyncDisposable /// ImportCompleted (recommended replacement) [Obsolete("Please use ICompositionGpuImportedObject.ImportCompleted instead")] [EditorBrowsable(EditorBrowsableState.Never)] - Task ImportCompeted => ImportCompleted; + Task ImportCompeted { get; } /// /// Indicates if the device context this instance is associated with is no longer available diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs index 9eea228e84..12a252ed96 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionInterop.cs @@ -74,6 +74,8 @@ abstract class CompositionGpuImportedObjectBase : ICompositionGpuImportedObject public abstract void Dispose(); public Task ImportCompleted { get; } + + public Task ImportCompeted => ImportCompleted; public bool IsLost => Context.IsLost; public ValueTask DisposeAsync() => new(Compositor.InvokeServerJobAsync(() => From 081803b292ce66c179441bfc9017019871271c98 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 13 Jul 2023 17:20:23 -0400 Subject: [PATCH 13/59] Add api change suppression for ImportCompleted --- api/Avalonia.nupkg.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 api/Avalonia.nupkg.xml diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml new file mode 100644 index 0000000000..6a7d53544e --- /dev/null +++ b/api/Avalonia.nupkg.xml @@ -0,0 +1,10 @@ + + + + + CP0006 + P:Avalonia.Rendering.Composition.ICompositionGpuImportedObject.ImportCompleted + baseline/netstandard2.0/Avalonia.Base.dll + target/netstandard2.0/Avalonia.Base.dll + + \ No newline at end of file From 4c1d7b4a204e437ac0f1e648361522753d650800 Mon Sep 17 00:00:00 2001 From: startewho Date: Fri, 14 Jul 2023 08:38:00 +0800 Subject: [PATCH 14/59] Update StringUtils.cs --- src/Avalonia.Controls/Utils/StringUtils.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Utils/StringUtils.cs b/src/Avalonia.Controls/Utils/StringUtils.cs index b664687650..0ae1ef0b97 100644 --- a/src/Avalonia.Controls/Utils/StringUtils.cs +++ b/src/Avalonia.Controls/Utils/StringUtils.cs @@ -94,7 +94,11 @@ namespace Avalonia.Controls.Utils return true; } } - return true; + else + { + return true; + } + } switch (codepoint.GeneralCategory) From 4e8e78886640ed84c89b7debb5c78957fb10829c Mon Sep 17 00:00:00 2001 From: startewho Date: Fri, 14 Jul 2023 09:33:28 +0800 Subject: [PATCH 15/59] handled --- src/Avalonia.Controls/ComboBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index bce1a3685f..fb27b3da22 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -270,6 +270,7 @@ namespace Avalonia.Controls { if (_popup?.IsInsidePopup(source) == true) { + e.Handled = true; return; } } From f89a0b2ac9323b38cd9c376f8bf2ed30814a1bd5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Jul 2023 10:57:45 +0200 Subject: [PATCH 16/59] Added failing test for #11220. --- .../SelectingItemsControlTests_SelectedValue.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs index e9b8895174..9ea12c62fb 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs @@ -268,6 +268,22 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.True(called); } + [Fact] + public void Handles_Null_SelectedItem_When_SelectedValueBinding_Assigned() + { + // Issue #11220 + var items = new object[] { null }; + var sic = new SelectingItemsControl + { + ItemsSource = items, + SelectedIndex = 0, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + Assert.Null(sic.SelectedValue); + } + private static FuncControlTemplate Template() { return new FuncControlTemplate((control, scope) => From c7da9c4bb86cc3583e796a45106ebb9fb8e6ad0d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 14 Jul 2023 11:01:38 +0200 Subject: [PATCH 17/59] Allow null item in BindingHelper.Evaluate. Fixes #11220. --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index e22d03273a..66ed1a27e2 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -1395,10 +1395,8 @@ namespace Avalonia.Controls.Primitives public object Evaluate(object? dataContext) { - dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); - // Only update the DataContext if necessary - if (!dataContext.Equals(DataContext)) + if (!Equals(dataContext, DataContext)) DataContext = dataContext; return GetValue(ValueProperty); From 43e1d99bdd70315aa069f867928a053b0b22eb23 Mon Sep 17 00:00:00 2001 From: Lighto Date: Fri, 14 Jul 2023 15:44:14 +0300 Subject: [PATCH 18/59] Added support for Mica Light & Dark based on Win11 --- samples/ControlCatalog/MainView.xaml | 3 +- .../WindowTransparencyLevel.cs | 9 +- .../WinRT/Composition/D2DEffects.cs | 3 + .../WinRT/Composition/WinUIEffectBase.cs | 104 ++++++++++++++++++ .../Composition/WinUiCompositedWindow.cs | 27 +++-- .../Composition/WinUiCompositionShared.cs | 8 +- .../Composition/WinUiCompositionUtils.cs | 75 ++++++++++++- src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs | 3 +- .../WinRT/WinRTPropertyValue.cs | 23 +++- src/Windows/Avalonia.Win32/WindowImpl.cs | 22 +++- 10 files changed, 253 insertions(+), 24 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 7ed2d67379..0a1621d02a 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -226,7 +226,8 @@ Transparent Blur AcrylicBlur - Mica + MicaLight + MicaDark - /// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11 + /// The window background is based on desktop wallpaper tint with a light blur. This will only work on Windows 11 /// - public static WindowTransparencyLevel Mica { get; } = new(nameof(Mica)); + public static WindowTransparencyLevel MicaLight { get; } = new(nameof(MicaLight)); + + /// + /// The window background is based on desktop wallpaper tint with a dark blur. This will only work on Windows 11 + /// + public static WindowTransparencyLevel MicaDark { get; } = new(nameof(MicaDark)); public override string ToString() { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/D2DEffects.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/D2DEffects.cs index bef5a55b06..98e1077885 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/D2DEffects.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/D2DEffects.cs @@ -28,6 +28,9 @@ namespace Avalonia.Win32.WinRT.Composition public static readonly Guid CLSID_D2D1Border = new Guid(0x2A2D49C0, 0x4ACF, 0x43C7, 0x8C, 0x6A, 0x7C, 0x4A, 0x27, 0x87, 0x4D, 0x27); + public static readonly Guid CLSID_D2D1Opacity = + new Guid("811d79a4-de28-4454-8094-c64685f8bd4c"); + public static readonly Guid CLSID_D2D1Brightness = new Guid(0x8CEA8D1E, 0x77B0, 0x4986, 0xB3, 0xB9, 0x2F, 0x0C, 0x0E, 0xAE, 0x78, 0x87); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs index 1a922b4acd..10dbcfcc6b 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUIEffectBase.cs @@ -52,6 +52,110 @@ namespace Avalonia.Win32.WinRT.Composition _sources = null; } } + + class BorderEffect : WinUIEffectBase + { + private readonly int _x; + private readonly int _y; + public override Guid EffectId => D2DEffects.CLSID_D2D1Border; + public override uint PropertyCount => 2; + + public BorderEffect(int x, int y, params IGraphicsEffectSource[] _sources):base(_sources) + { + _x = x; + _y = y; + } + + public override IPropertyValue? GetProperty(uint index) + { + if (index == 0) + return new WinRTPropertyValue((uint)_x); + if (index == 1) + return new WinRTPropertyValue((uint)_y); + return null; + } + } + + class BlendEffect : WinUIEffectBase + { + private readonly int _mode; + + public BlendEffect(int mode, params IGraphicsEffectSource[] _sources) : base(_sources) + { + _mode = mode; + } + + public override Guid EffectId => D2DEffects.CLSID_D2D1Blend; + public override uint PropertyCount => 1; + + public override IPropertyValue? GetProperty(uint index) + { + if (index == 0) + return new WinRTPropertyValue((uint)_mode); + return null; + } + } + + class CompositeStepEffect : WinUIEffectBase + { + private readonly float _mode; + + public CompositeStepEffect(int mode, params IGraphicsEffectSource[] _sources) : base(_sources) + { + _mode = mode; + } + + public override Guid EffectId => D2DEffects.CLSID_D2D1Composite; + public override uint PropertyCount => 1; + + public override IPropertyValue? GetProperty(uint index) + { + if (index == 0) + return new WinRTPropertyValue((uint)_mode); + return null; + } + } + + class OpacityEffect : WinUIEffectBase + { + private readonly float _opacity; + + public OpacityEffect(float opacity, params IGraphicsEffectSource[] _sources) : base(_sources) + { + _opacity = opacity; + } + + public override Guid EffectId => D2DEffects.CLSID_D2D1Opacity; + public override uint PropertyCount => 1; + + public override IPropertyValue? GetProperty(uint index) + { + if (index == 0) + return new WinRTPropertyValue(_opacity); + return null; + } + } + + class ColorSourceEffect : WinUIEffectBase + { + private readonly float[] _color; + + public ColorSourceEffect(float[] color) + { + _color = color; + } + + public override Guid EffectId => D2DEffects.CLSID_D2D1Flood; + public override uint PropertyCount => 1; + + public override IPropertyValue? GetProperty(uint index) + { + if (index == 0) + return new WinRTPropertyValue(_color); + return null; + } + } + internal class WinUIGaussianBlurEffect : WinUIEffectBase { diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs index 2f22ba99f9..d87c8d1361 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs @@ -12,7 +12,8 @@ internal class WinUiCompositedWindow : IDisposable public EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo WindowInfo { get; } private readonly WinUiCompositionShared _shared; private readonly ICompositionRoundedRectangleGeometry? _compositionRoundedRectangleGeometry; - private readonly IVisual? _mica; + private readonly IVisual? _micaLight; + private readonly IVisual? _micaDark; private readonly IVisual _blur; private readonly IVisual _visual; private PixelSize _size; @@ -25,7 +26,8 @@ internal class WinUiCompositedWindow : IDisposable { _compositionRoundedRectangleGeometry?.Dispose(); _blur.Dispose(); - _mica?.Dispose(); + _micaLight?.Dispose(); + _micaDark?.Dispose(); _visual.Dispose(); _surfaceBrush.Dispose(); _target.Dispose(); @@ -50,14 +52,20 @@ internal class WinUiCompositedWindow : IDisposable _target.SetRoot(containerVisual); _blur = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.BlurBrush); - if (shared.MicaBrush != null) + if (shared.MicaBrushLight != null) { - _mica = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrush); - containerChildren.InsertAtTop(_mica); + _micaLight = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrushLight); + containerChildren.InsertAtTop(_micaLight); + } + + if (shared.MicaBrushDark != null) + { + _micaDark = WinUiCompositionUtils.CreateBlurVisual(shared.Compositor, shared.MicaBrushDark); + containerChildren.InsertAtTop(_micaDark); } _compositionRoundedRectangleGeometry = - WinUiCompositionUtils.ClipVisual(shared.Compositor, backdropCornerRadius, _blur, _mica); + WinUiCompositionUtils.ClipVisual(shared.Compositor, backdropCornerRadius, _blur, _micaLight, _micaDark); containerChildren.InsertAtTop(_blur); using var spriteVisual = shared.Compositor.CreateSpriteVisual(); @@ -79,12 +87,13 @@ internal class WinUiCompositedWindow : IDisposable { lock (_shared.SyncRoot) { - _blur.SetIsVisible(blurEffect == BlurEffect.Acrylic - || blurEffect == BlurEffect.Mica && _mica == null ? + || (blurEffect == BlurEffect.MicaLight && _micaLight == null) || + (blurEffect == BlurEffect.MicaDark && _micaDark == null) ? 1 : 0); - _mica?.SetIsVisible(blurEffect == BlurEffect.Mica ? 1 : 0); + _micaLight?.SetIsVisible(blurEffect == BlurEffect.MicaLight ? 1 : 0); + _micaDark?.SetIsVisible(blurEffect == BlurEffect.MicaDark ? 1 : 0); } } diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs index b3a328d097..5c7f490710 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs @@ -9,6 +9,8 @@ internal class WinUiCompositionShared : IDisposable public ICompositor5 Compositor5 { get; } public ICompositorDesktopInterop DesktopInterop { get; } public ICompositionBrush BlurBrush { get; } + public ICompositionBrush? MicaBrushLight { get; } + public ICompositionBrush? MicaBrushDark { get; } public ICompositionBrush? MicaBrush { get; } public object SyncRoot { get; } = new(); @@ -21,14 +23,16 @@ internal class WinUiCompositionShared : IDisposable Compositor = compositor.CloneReference(); Compositor5 = compositor.QueryInterface(); BlurBrush = WinUiCompositionUtils.CreateAcrylicBlurBackdropBrush(compositor); - MicaBrush = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor); + MicaBrushLight = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor, 242, 0.6f); + MicaBrushDark = WinUiCompositionUtils.CreateMicaBackdropBrush(compositor, 32, 0.8f); DesktopInterop = compositor.QueryInterface(); } public void Dispose() { BlurBrush.Dispose(); - MicaBrush?.Dispose(); + MicaBrushLight?.Dispose(); + MicaBrushDark?.Dispose(); DesktopInterop.Dispose(); Compositor.Dispose(); Compositor5.Dispose(); diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs index 7b970868df..1a130698ee 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using MicroCom.Runtime; @@ -5,16 +6,75 @@ namespace Avalonia.Win32.WinRT.Composition; internal static class WinUiCompositionUtils { - public static ICompositionBrush? CreateMicaBackdropBrush(ICompositor compositor) + public static ICompositionBrush? CreateMicaBackdropBrush(ICompositor compositor, float color, float opacity) { if (Win32Platform.WindowsVersion.Build < 22000) return null; + using var backDropParameterFactory = + NativeWinRTMethods.CreateActivationFactory( + "Windows.UI.Composition.CompositionEffectSourceParameter"); + + + var tint = new[] { color / 255f, color / 255f, color / 255f, 255f / 255f }; + + using var tintColorEffect = new ColorSourceEffect(tint); + + + using var tintOpacityEffect = new OpacityEffect(1.0f, tintColorEffect); + using var tintOpacityEffectFactory = compositor.CreateEffectFactory(tintOpacityEffect); + using var tintOpacityEffectBrushEffect = tintOpacityEffectFactory.CreateBrush(); + using var tintOpacityEffectBrush = tintOpacityEffectBrushEffect.QueryInterface(); + + using var luminosityColorEffect = new ColorSourceEffect(tint); + + using var luminosityOpacityEffect = new OpacityEffect(opacity, luminosityColorEffect); + using var luminosityOpacityEffectFactory = compositor.CreateEffectFactory(luminosityOpacityEffect); + using var luminosityOpacityEffectBrushEffect = luminosityOpacityEffectFactory.CreateBrush(); + using var luminosityOpacityEffectBrush = + luminosityOpacityEffectBrushEffect.QueryInterface(); + + + // using var backDropParameterAsSource = GetParameterSource("BlurredWallpaperBackdrop", backDropParameterFactory, out var backdropHandle); + // using var backdropCompositionBrsuh = backDropParameterAsSource.QueryInterface(); using var compositorWithBlurredWallpaperBackdropBrush = compositor.QueryInterface(); using var blurredWallpaperBackdropBrush = compositorWithBlurredWallpaperBackdropBrush?.TryCreateBlurredWallpaperBackdropBrush(); - return blurredWallpaperBackdropBrush?.QueryInterface(); + using var micaBackdropBrush = blurredWallpaperBackdropBrush?.QueryInterface(); + + + using var backgroundParameterAsSource = + GetParameterSource("Background", backDropParameterFactory, out var backgroundHandle); + using var foregroundParameterAsSource = + GetParameterSource("Foreground", backDropParameterFactory, out var foregroundHandle); + + using var luminosityBlendEffect = + new BlendEffect(23, backgroundParameterAsSource, foregroundParameterAsSource); + using var luminosityBlendEffectFactory = compositor.CreateEffectFactory(luminosityBlendEffect); + using var luminosityBlendEffectBrush = luminosityBlendEffectFactory.CreateBrush(); + using var luminosityBlendEffectBrush1 = luminosityBlendEffectBrush.QueryInterface(); + luminosityBlendEffectBrush.SetSourceParameter(backgroundHandle, micaBackdropBrush); + luminosityBlendEffectBrush.SetSourceParameter(foregroundHandle, luminosityOpacityEffectBrush); + + + using var backgroundParameterAsSource1 = + GetParameterSource("Background", backDropParameterFactory, out var backgroundHandle1); + using var foregroundParameterAsSource1 = + GetParameterSource("Foreground", backDropParameterFactory, out var foregroundHandle1); + + using var colorBlendEffect = + new BlendEffect(22, backgroundParameterAsSource1, foregroundParameterAsSource1); + using var colorBlendEffectFactory = compositor.CreateEffectFactory(colorBlendEffect); + using var colorBlendEffectBrush = colorBlendEffectFactory.CreateBrush(); + colorBlendEffectBrush.SetSourceParameter(backgroundHandle1, luminosityBlendEffectBrush1); + colorBlendEffectBrush.SetSourceParameter(foregroundHandle1, tintOpacityEffectBrush); + + + // colorBlendEffectBrush.SetSourceParameter(backgroundHandle, micaBackdropBrush); + + using var micaBackdropBrush1 = colorBlendEffectBrush.QueryInterface(); + return micaBackdropBrush1.CloneReference(); } public static ICompositionBrush CreateAcrylicBlurBackdropBrush(ICompositor compositor) @@ -97,4 +157,15 @@ internal static class WinUiCompositionUtils brush?.Dispose(); } } + + private static IGraphicsEffectSource GetParameterSource(string name, + ICompositionEffectSourceParameterFactory backDropParameterFactory, out IntPtr handle) + { + var backdropString = new HStringInterop(name); + var backDropParameter = + backDropParameterFactory.Create(backdropString.Handle); + var backDropParameterAsSource = backDropParameter.QueryInterface(); + handle = backdropString.Handle; + return backDropParameterAsSource; + } } diff --git a/src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs b/src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs index a3918d9ae6..5f2e752af8 100644 --- a/src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs +++ b/src/Windows/Avalonia.Win32/WinRT/IBlurHost.cs @@ -4,7 +4,8 @@ { None, Acrylic, - Mica + MicaLight, + MicaDark } internal interface IBlurHost diff --git a/src/Windows/Avalonia.Win32/WinRT/WinRTPropertyValue.cs b/src/Windows/Avalonia.Win32/WinRT/WinRTPropertyValue.cs index 684e7ff7b5..8874902d4e 100644 --- a/src/Windows/Avalonia.Win32/WinRT/WinRTPropertyValue.cs +++ b/src/Windows/Avalonia.Win32/WinRT/WinRTPropertyValue.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Avalonia.Win32.WinRT @@ -16,7 +17,15 @@ namespace Avalonia.Win32.WinRT UInt32 = u; Type = PropertyType.UInt32; } - + + public WinRTPropertyValue(float[] uiColor) + { + Type = PropertyType.SingleArray; + _singleArray = uiColor; + } + + private readonly float[]? _singleArray; + public PropertyType Type { get; } public int IsNumericScalar { get; } public byte UInt8 { get; } @@ -62,7 +71,17 @@ namespace Avalonia.Win32.WinRT public unsafe ulong* GetUInt64Array(uint* __valueSize) => throw NotImplemented; - public unsafe float* GetSingleArray(uint* __valueSize) => throw NotImplemented; + public unsafe float* GetSingleArray(uint* __valueSize) + { + if (_singleArray == null) + throw NotImplemented; + *__valueSize = (uint)_singleArray.Length; + var allocCoTaskMem = Marshal.AllocCoTaskMem(_singleArray.Length * Unsafe.SizeOf()); + Marshal.Copy(_singleArray, 0, allocCoTaskMem, _singleArray.Length); + float* s = (float*)allocCoTaskMem; + + return s; + } public unsafe double* GetDoubleArray(uint* __valueSize) => throw NotImplemented; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 057cdb2db0..2b16bbe275 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -374,8 +374,10 @@ namespace Avalonia.Win32 SetTransparencyBlur(windowsVersion); else if (level == WindowTransparencyLevel.AcrylicBlur) SetTransparencyAcrylicBlur(windowsVersion); - else if (level == WindowTransparencyLevel.Mica) - SetTransparencyMica(windowsVersion); + else if (level == WindowTransparencyLevel.MicaLight) + SetTransparencyMicaLight(windowsVersion); + else if (level == WindowTransparencyLevel.MicaDark) + SetTransparencyMicaDark(windowsVersion); TransparencyLevel = level; return; @@ -418,7 +420,7 @@ namespace Avalonia.Win32 return windowsVersion >= WinUiCompositionShared.MinAcrylicVersion; // Mica is supported on Windows >= 10.0.22000. - if (level == WindowTransparencyLevel.Mica) + if (level == WindowTransparencyLevel.MicaLight || level == WindowTransparencyLevel.MicaDark) return windowsVersion >= WinUiCompositionShared.MinHostBackdropVersion; return false; @@ -467,14 +469,24 @@ namespace Avalonia.Win32 _blurHost?.SetBlur(BlurEffect.Acrylic); } - private void SetTransparencyMica(Version windowsVersion) + private void SetTransparencyMicaLight(Version windowsVersion) { // Mica only supported with composition on Windows >= 10.0.22000. if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion) return; SetUseHostBackdropBrush(false); - _blurHost?.SetBlur(BlurEffect.Mica); + _blurHost?.SetBlur(BlurEffect.MicaLight); + } + + private void SetTransparencyMicaDark(Version windowsVersion) + { + // Mica only supported with composition on Windows >= 10.0.22000. + if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion) + return; + + SetUseHostBackdropBrush(false); + _blurHost?.SetBlur(BlurEffect.MicaDark); } private void SetAccentState(AccentState state) From 432dafac91dc4cb61ffd338f7d8afcf5d4ab6540 Mon Sep 17 00:00:00 2001 From: Lighto Date: Fri, 14 Jul 2023 18:38:35 +0300 Subject: [PATCH 19/59] Removed unused old property --- .../Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs index 5c7f490710..7a775afb3a 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs @@ -11,7 +11,6 @@ internal class WinUiCompositionShared : IDisposable public ICompositionBrush BlurBrush { get; } public ICompositionBrush? MicaBrushLight { get; } public ICompositionBrush? MicaBrushDark { get; } - public ICompositionBrush? MicaBrush { get; } public object SyncRoot { get; } = new(); public static readonly Version MinWinCompositionVersion = new(10, 0, 17134); From 217a3e0624d0b116f5d279ca026e926107a6b0a0 Mon Sep 17 00:00:00 2001 From: Vitalii Orazov Date: Fri, 14 Jul 2023 22:31:01 +0300 Subject: [PATCH 20/59] remove duplicates in InternalsVisibleTo --- src/Avalonia.Base/Avalonia.Base.csproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index ddc2b7effb..16eb09de65 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -43,21 +43,15 @@ - - - - - - From c126db5b554349893e45e43a0cf289a601dedc20 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Jul 2023 18:29:40 +0600 Subject: [PATCH 21/59] Added AvaloniaFilePreview target --- packages/Avalonia/AvaloniaBuildTasks.targets | 26 ++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 33f22f4d02..6af35ff0d2 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -144,4 +144,30 @@ + + + Build + + + + + http://127.0.0.1:6001 + $(OutputPath)/$(AssemblyName).dll + MainWindow.axaml + $(APreviewExecutable) + $([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.deps.json')) + $([System.IO.Path]::ChangeExtension('$(APreviewExecutable)', '.runtimeconfig.json')) + $([System.IO.Path]::GetFullPath('$(APreviewFile)')) + + + + + + + + + + + + From 55c03069b1e9cf681cde74a476038118296c340f Mon Sep 17 00:00:00 2001 From: Lighto Date: Sat, 15 Jul 2023 22:18:59 +0300 Subject: [PATCH 22/59] Changed Mica dark/light based on theme --- samples/ControlCatalog/MainView.xaml | 3 +- .../WindowTransparencyLevel.cs | 9 ++--- .../Composition/WinUiCompositedWindow.cs | 3 -- src/Windows/Avalonia.Win32/WindowImpl.cs | 33 +++++++++---------- 4 files changed, 19 insertions(+), 29 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 0a1621d02a..7ed2d67379 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -226,8 +226,7 @@ Transparent Blur AcrylicBlur - MicaLight - MicaDark + Mica - /// The window background is based on desktop wallpaper tint with a light blur. This will only work on Windows 11 + /// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11 /// - public static WindowTransparencyLevel MicaLight { get; } = new(nameof(MicaLight)); + public static WindowTransparencyLevel Mica { get; } = new(nameof(Mica)); - /// - /// The window background is based on desktop wallpaper tint with a dark blur. This will only work on Windows 11 - /// - public static WindowTransparencyLevel MicaDark { get; } = new(nameof(MicaDark)); - public override string ToString() { return _value; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs index d87c8d1361..d720e525d3 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositedWindow.cs @@ -76,9 +76,6 @@ internal class WinUiCompositedWindow : IDisposable using var compositionBrush = _surfaceBrush.QueryInterface(); spriteVisual.SetBrush(compositionBrush); _target.SetRoot(containerVisual); - - - } public void SetSurface(ICompositionSurface surface) => _surfaceBrush.SetSurface(surface); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 2b16bbe275..044c2cad67 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -106,6 +106,7 @@ namespace Avalonia.Win32 private static POINTER_PEN_INFO[]? s_historyPenInfos; private static POINTER_INFO[]? s_historyInfos; private static MOUSEMOVEPOINT[]? s_mouseHistoryInfos; + private PlatformThemeVariant _currentThemeVariant; public WindowImpl() { @@ -374,10 +375,8 @@ namespace Avalonia.Win32 SetTransparencyBlur(windowsVersion); else if (level == WindowTransparencyLevel.AcrylicBlur) SetTransparencyAcrylicBlur(windowsVersion); - else if (level == WindowTransparencyLevel.MicaLight) - SetTransparencyMicaLight(windowsVersion); - else if (level == WindowTransparencyLevel.MicaDark) - SetTransparencyMicaDark(windowsVersion); + else if (level == WindowTransparencyLevel.Mica) + SetTransparencyMica(windowsVersion); TransparencyLevel = level; return; @@ -420,7 +419,7 @@ namespace Avalonia.Win32 return windowsVersion >= WinUiCompositionShared.MinAcrylicVersion; // Mica is supported on Windows >= 10.0.22000. - if (level == WindowTransparencyLevel.MicaLight || level == WindowTransparencyLevel.MicaDark) + if (level == WindowTransparencyLevel.Mica) return windowsVersion >= WinUiCompositionShared.MinHostBackdropVersion; return false; @@ -469,24 +468,19 @@ namespace Avalonia.Win32 _blurHost?.SetBlur(BlurEffect.Acrylic); } - private void SetTransparencyMicaLight(Version windowsVersion) + private void SetTransparencyMica(Version windowsVersion) { // Mica only supported with composition on Windows >= 10.0.22000. if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion) return; SetUseHostBackdropBrush(false); - _blurHost?.SetBlur(BlurEffect.MicaLight); - } - - private void SetTransparencyMicaDark(Version windowsVersion) - { - // Mica only supported with composition on Windows >= 10.0.22000. - if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion) - return; - - SetUseHostBackdropBrush(false); - _blurHost?.SetBlur(BlurEffect.MicaDark); + _blurHost?.SetBlur(_currentThemeVariant switch + { + PlatformThemeVariant.Light => BlurEffect.MicaLight, + PlatformThemeVariant.Dark => BlurEffect.MicaDark, + _ => throw new ArgumentOutOfRangeException() + }); } private void SetAccentState(AccentState state) @@ -790,6 +784,7 @@ namespace Avalonia.Win32 public unsafe void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { + _currentThemeVariant = themeVariant; if (Win32Platform.WindowsVersion.Build >= 22000) { var pvUseBackdropBrush = themeVariant == PlatformThemeVariant.Dark ? 1 : 0; @@ -798,6 +793,10 @@ namespace Avalonia.Win32 (int)DwmWindowAttribute.DWMWA_USE_IMMERSIVE_DARK_MODE, &pvUseBackdropBrush, sizeof(int)); + if (TransparencyLevel == WindowTransparencyLevel.Mica) + { + SetTransparencyMica(Win32Platform.WindowsVersion); + } } } From 7a0e0c41a40baf60cce214ee6756f95ca75fd7a8 Mon Sep 17 00:00:00 2001 From: Lighto Date: Sat, 15 Jul 2023 22:20:50 +0300 Subject: [PATCH 23/59] Removed unnecessary spaces --- src/Avalonia.Controls/WindowTransparencyLevel.cs | 2 +- .../WinRT/Composition/WinUiCompositionUtils.cs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/WindowTransparencyLevel.cs b/src/Avalonia.Controls/WindowTransparencyLevel.cs index 9604a7ef5c..bce6e21e68 100644 --- a/src/Avalonia.Controls/WindowTransparencyLevel.cs +++ b/src/Avalonia.Controls/WindowTransparencyLevel.cs @@ -36,7 +36,7 @@ public readonly record struct WindowTransparencyLevel /// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11 /// public static WindowTransparencyLevel Mica { get; } = new(nameof(Mica)); - + public override string ToString() { return _value; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs index 1a130698ee..29c1e1fe27 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionUtils.cs @@ -33,10 +33,7 @@ internal static class WinUiCompositionUtils using var luminosityOpacityEffectBrushEffect = luminosityOpacityEffectFactory.CreateBrush(); using var luminosityOpacityEffectBrush = luminosityOpacityEffectBrushEffect.QueryInterface(); - - - // using var backDropParameterAsSource = GetParameterSource("BlurredWallpaperBackdrop", backDropParameterFactory, out var backdropHandle); - // using var backdropCompositionBrsuh = backDropParameterAsSource.QueryInterface(); + using var compositorWithBlurredWallpaperBackdropBrush = compositor.QueryInterface(); using var blurredWallpaperBackdropBrush = From 746a027bd80e2f1b8f837abad11a851c17125077 Mon Sep 17 00:00:00 2001 From: Sorien Date: Sat, 15 Jul 2023 22:19:12 +0200 Subject: [PATCH 24/59] Fix typo --- src/Avalonia.Native/NativePlatformSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/NativePlatformSettings.cs b/src/Avalonia.Native/NativePlatformSettings.cs index 53d3da0378..42d05d831c 100644 --- a/src/Avalonia.Native/NativePlatformSettings.cs +++ b/src/Avalonia.Native/NativePlatformSettings.cs @@ -23,7 +23,7 @@ internal class NativePlatformSettings : DefaultPlatformSettings AvnPlatformThemeVariant.Dark => (PlatformThemeVariant.Dark, ColorContrastPreference.NoPreference), AvnPlatformThemeVariant.Light => (PlatformThemeVariant.Light, ColorContrastPreference.NoPreference), AvnPlatformThemeVariant.HighContrastDark => (PlatformThemeVariant.Dark, ColorContrastPreference.High), - AvnPlatformThemeVariant.HighContrastLight => (PlatformThemeVariant.Dark, ColorContrastPreference.High), + AvnPlatformThemeVariant.HighContrastLight => (PlatformThemeVariant.Light, ColorContrastPreference.High), _ => throw new ArgumentOutOfRangeException() }; var color = _platformSettings.AccentColor; From cbfa327aee6564a0a42ead2c68c15d33cb47fa46 Mon Sep 17 00:00:00 2001 From: Zachary Weldon Date: Sat, 15 Jul 2023 20:22:54 -0700 Subject: [PATCH 25/59] added clear function to combobox --- src/Avalonia.Controls/ComboBox.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index fb27b3da22..bd05d4947c 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -517,5 +517,14 @@ namespace Avalonia.Controls } } } + + /// + /// Clears the selection + /// + public void Clear() + { + SelectedItem = null; + SelectedIndex = -1; + } } } From 6eff41172fc88b0472fd3c9b04dd6c6b4eb19548 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 16 Jul 2023 00:21:01 +0200 Subject: [PATCH 26/59] Generate symbol packages --- build/SourceLink.props | 8 ++------ nukebuild/Numerge | 2 +- nukebuild/_build.csproj | 12 +++++------- .../Avalonia.Analyzers/Avalonia.Analyzers.csproj | 1 + .../Avalonia.Generators/Avalonia.Generators.csproj | 1 + 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/build/SourceLink.props b/build/SourceLink.props index dd7ecc8d2a..b0f1f2c2cc 100644 --- a/build/SourceLink.props +++ b/build/SourceLink.props @@ -1,9 +1,9 @@ true - false + true + snupkg true - $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb @@ -14,10 +14,6 @@ true - - embedded - - diff --git a/nukebuild/Numerge b/nukebuild/Numerge index aef10ae67d..7908ee8e86 160000 --- a/nukebuild/Numerge +++ b/nukebuild/Numerge @@ -1 +1 @@ -Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5 +Subproject commit 7908ee8e8619180d04a4de3b0210c009db400e78 diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 43453833d7..64eccbbb3f 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -8,8 +8,8 @@ 1 net7.0 - - + + @@ -32,9 +32,9 @@ - - - + + + @@ -43,7 +43,5 @@ dirs.proj - - diff --git a/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj b/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj index c27801db61..05d72618a9 100644 --- a/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj +++ b/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj @@ -5,6 +5,7 @@ Avalonia.Analyzers true true + false true diff --git a/src/tools/Avalonia.Generators/Avalonia.Generators.csproj b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj index cd1e2fcaba..c6992f9438 100644 --- a/src/tools/Avalonia.Generators/Avalonia.Generators.csproj +++ b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj @@ -5,6 +5,7 @@ Avalonia.Generators $(DefineConstants);XAMLX_INTERNAL true + false true true From 12e1d5ef104bb46731511e5ed3245517be822537 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 16 Jul 2023 01:58:50 +0200 Subject: [PATCH 27/59] Avoid api validation for symbol packages --- nukebuild/ApiDiffValidation.cs | 5 +++-- nukebuild/Build.cs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nukebuild/ApiDiffValidation.cs b/nukebuild/ApiDiffValidation.cs index bd576250bd..483bfd336a 100644 --- a/nukebuild/ApiDiffValidation.cs +++ b/nukebuild/ApiDiffValidation.cs @@ -38,7 +38,8 @@ public static class ApiDiffValidation var left = new List(); var right = new List(); - var suppressionFile = Path.Combine(suppressionFilesFolder, GetPackageId(packagePath) + ".nupkg.xml"); + var packageId = GetPackageId(packagePath); + var suppressionFile = Path.Combine(suppressionFilesFolder, packageId + ".nupkg.xml"); // Don't use Path.Combine with these left and right tool parameters. // Microsoft.DotNet.ApiCompat.Tool is stupid and treats '/' and '\' as different assemblies in suppression files. @@ -57,7 +58,7 @@ public static class ApiDiffValidation e.target == baselineDll.target && e.entry.Name == baselineDll.entry.Name); if (targetDll.entry is null) { - throw new InvalidOperationException($"Some assemblies are missing in the new package: {baselineDll.entry.Name} for {baselineDll.target}"); + throw new InvalidOperationException($"Some assemblies are missing in the new package {packageId}: {baselineDll.entry.Name} for {baselineDll.target}"); } var targetDllPath = $"target/{targetDll.target}/{targetDll.entry.Name}"; diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index b82c446249..bbfc28aa9f 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -288,7 +288,7 @@ partial class Build : NukeBuild .Executes(async () => { await Task.WhenAll( - Directory.GetFiles(Parameters.NugetRoot).Select(nugetPackage => ApiDiffValidation.ValidatePackage( + Directory.GetFiles(Parameters.NugetRoot, "*.nupkg").Select(nugetPackage => ApiDiffValidation.ValidatePackage( ApiCompatTool, nugetPackage, Parameters.ApiValidationBaseline, Parameters.ApiValidationSuppressionFiles, Parameters.UpdateApiValidationSuppression))); }); From 97d4e7d9a1a57086173bc279996348aceff597bf Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 16 Jul 2023 11:38:59 +0200 Subject: [PATCH 28/59] Update Numerge --- nukebuild/Numerge | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nukebuild/Numerge b/nukebuild/Numerge index 7908ee8e86..9738c6121f 160000 --- a/nukebuild/Numerge +++ b/nukebuild/Numerge @@ -1 +1 @@ -Subproject commit 7908ee8e8619180d04a4de3b0210c009db400e78 +Subproject commit 9738c6121fdd143c78d3e25686a7e4e13c00f586 From 6a48527a52df21ea1a6f014e55952bddaa54c097 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 16 Jul 2023 23:16:34 +0200 Subject: [PATCH 29/59] Remove unnecessary cast. --- src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs index ff8ff69d5e..739f0ac251 100644 --- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -33,8 +33,7 @@ namespace Avalonia.Win32.Automation return null; var p = WindowImpl.PointToClient(new PixelPoint((int)x, (int)y)); - var peer = (WindowBaseAutomationPeer)Peer; - var found = InvokeSync(() => peer.GetPeerFromPoint(p)); + var found = InvokeSync(() => Peer.GetPeerFromPoint(p)); var result = GetOrCreate(found) as IRawElementProviderFragment; return result; } From 7f4f01b11a2863d326a48e8ce8c42233a51b1edc Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 17 Jul 2023 02:40:47 +0200 Subject: [PATCH 30/59] Modernized argument check syntax --- src/Avalonia.Base/Threading/Dispatcher.Invoke.cs | 4 ++-- src/Avalonia.Base/Threading/DispatcherTimer.cs | 12 ++++++------ .../Collections/DataGridCollectionView.cs | 4 ++-- .../Automation/Peers/ScrollViewerAutomationPeer.cs | 4 ++-- .../DateTimePickers/DateTimePickerPanel.cs | 4 ++-- .../ScrollViewerIRefreshInfoProviderAdapter.cs | 10 +++++----- .../Utils/VirtualizingSnapPointsList.cs | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs index 25be5779b9..5995a03758 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs @@ -98,7 +98,7 @@ public partial class Dispatcher if (timeout.TotalMilliseconds < 0 && timeout != TimeSpan.FromMilliseconds(-1)) { - throw new ArgumentOutOfRangeException("timeout"); + throw new ArgumentOutOfRangeException(nameof(timeout)); } // Fast-Path: if on the same thread, and invoking at Send priority, @@ -220,7 +220,7 @@ public partial class Dispatcher if (timeout.TotalMilliseconds < 0 && timeout != TimeSpan.FromMilliseconds(-1)) { - throw new ArgumentOutOfRangeException("timeout"); + throw new ArgumentOutOfRangeException(nameof(timeout)); } // Fast-Path: if on the same thread, and invoking at Send priority, diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index 473b9eeefa..fbdeed4edc 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -112,11 +112,11 @@ public partial class DispatcherTimer bool updateOSTimer = false; if (value.TotalMilliseconds < 0) - throw new ArgumentOutOfRangeException("value", + throw new ArgumentOutOfRangeException(nameof(value), "TimeSpan period must be greater than or equal to zero."); if (value.TotalMilliseconds > Int32.MaxValue) - throw new ArgumentOutOfRangeException("value", + throw new ArgumentOutOfRangeException(nameof(value), "TimeSpan period must be less than or equal to Int32.MaxValue."); lock (_instanceLock) @@ -259,14 +259,14 @@ public partial class DispatcherTimer DispatcherPriority.Validate(priority, "priority"); if (priority == DispatcherPriority.Inactive) { - throw new ArgumentException("Specified priority is not valid.", "priority"); + throw new ArgumentException("Specified priority is not valid.", nameof(priority)); } if (interval.TotalMilliseconds < 0) - throw new ArgumentOutOfRangeException("interval", "TimeSpan period must be greater than or equal to zero."); + throw new ArgumentOutOfRangeException(nameof(interval), "TimeSpan period must be greater than or equal to zero."); if (interval.TotalMilliseconds > Int32.MaxValue) - throw new ArgumentOutOfRangeException("interval", + throw new ArgumentOutOfRangeException(nameof(interval), "TimeSpan period must be less than or equal to Int32.MaxValue."); @@ -349,4 +349,4 @@ public partial class DispatcherTimer // used by Dispatcher internal long DueTimeInMs { get; private set; } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs index 30517f4b00..0026c9e074 100644 --- a/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs +++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs @@ -807,7 +807,7 @@ namespace Avalonia.Collections { if (value < 0) { - throw new ArgumentOutOfRangeException("PageSize cannot have a negative value."); + throw new ArgumentOutOfRangeException(nameof(value), "PageSize cannot have a negative value."); } // if the Refresh is currently deferred, cache the desired PageSize @@ -1954,7 +1954,7 @@ namespace Avalonia.Collections // for indices larger than the count if (index >= Count || index < 0) { - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); } if (IsGrouping) diff --git a/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs index 835ed1c4af..674eb9a241 100644 --- a/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ScrollViewerAutomationPeer.cs @@ -146,12 +146,12 @@ namespace Avalonia.Automation.Peers if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0)) { - throw new ArgumentOutOfRangeException("horizontalPercent"); + throw new ArgumentOutOfRangeException(nameof(horizontalPercent)); } if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0)) { - throw new ArgumentOutOfRangeException("verticalPercent"); + throw new ArgumentOutOfRangeException(nameof(verticalPercent)); } var offset = Owner.Offset; diff --git a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs index 69b0ffe9a6..5a5c3fdf1d 100644 --- a/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs +++ b/src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs @@ -165,7 +165,7 @@ namespace Avalonia.Controls.Primitives set { if (value > MaximumValue || value < MinimumValue) - throw new ArgumentOutOfRangeException("SelectedValue"); + throw new ArgumentOutOfRangeException(nameof(value)); var sel = CoerceSelected(value); _selectedValue = sel; @@ -195,7 +195,7 @@ namespace Avalonia.Controls.Primitives set { if (value <= 0 || value > _range) - throw new ArgumentOutOfRangeException("Increment"); + throw new ArgumentOutOfRangeException(nameof(value)); _increment = value; UpdateHelperInfo(); var sel = CoerceSelected(SelectedValue); diff --git a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs index 4c1e0c2565..7ff02711d6 100644 --- a/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs +++ b/src/Avalonia.Controls/PullToRefresh/ScrollViewerIRefreshInfoProviderAdapter.cs @@ -98,14 +98,14 @@ namespace Avalonia.Controls.PullToRefresh if (_scrollViewer.Content == null) { - throw new ArgumentException(nameof(adaptee), "Adaptee's content property cannot be null."); + throw new ArgumentException("Adaptee's content property cannot be null.", nameof(adaptee)); } var content = adaptee.Content as Visual; if (content == null) { - throw new ArgumentException(nameof(adaptee), "Adaptee's content property must be a Visual"); + throw new ArgumentException("Adaptee's content property must be a Visual", nameof(adaptee)); } if (content.GetVisualParent() == null) @@ -118,7 +118,7 @@ namespace Avalonia.Controls.PullToRefresh if (content.Parent is not InputElement) { - throw new ArgumentException(nameof(adaptee), "Adaptee's content's parent must be a InputElement"); + throw new ArgumentException("Adaptee's content's parent must be a InputElement", nameof(adaptee)); } } @@ -194,12 +194,12 @@ namespace Avalonia.Controls.PullToRefresh var content = _scrollViewer?.Content as Visual; if (content == null) { - throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content property must be a Visual"); + throw new ArgumentException("Adaptee's content property must be a Visual", nameof(_scrollViewer)); } if (content.Parent is not InputElement parent) { - throw new ArgumentException(nameof(_scrollViewer), "Adaptee's content parent must be an InputElement"); + throw new ArgumentException("Adaptee's content parent must be an InputElement", nameof(_scrollViewer)); } MakeInteractionSource(parent); diff --git a/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs b/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs index f92756c59b..eaf501b45f 100644 --- a/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs +++ b/src/Avalonia.Controls/Utils/VirtualizingSnapPointsList.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls.Utils get { if(index < 0 || index >= Count) - throw new ArgumentOutOfRangeException("index"); + throw new ArgumentOutOfRangeException(nameof(index)); index += _start; From 15b42836b13f67623efc9d5b47db8a6b9f11bc1d Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 17 Jul 2023 02:50:25 +0200 Subject: [PATCH 31/59] string.Format converted to interpolated string --- src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs | 4 ++-- src/Avalonia.Remote.Protocol/MetsysBson.cs | 8 ++++---- src/Avalonia.X11/X11Structs.cs | 5 ++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs index b5be12fa54..30f9d8f380 100644 --- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs +++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs @@ -1113,11 +1113,11 @@ namespace Avalonia.Controls } if (value < Minimum) { - throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be greater than Minimum value of {0}", Minimum)); + throw new ArgumentOutOfRangeException(nameof(value), $"Value must be greater than Minimum value of {Minimum}"); } else if (value > Maximum) { - throw new ArgumentOutOfRangeException(nameof(value), string.Format("Value must be less than Maximum value of {0}", Maximum)); + throw new ArgumentOutOfRangeException(nameof(value), $"Value must be less than Maximum value of {Maximum}"); } } diff --git a/src/Avalonia.Remote.Protocol/MetsysBson.cs b/src/Avalonia.Remote.Protocol/MetsysBson.cs index 8966dd4206..9c5f92b6ac 100644 --- a/src/Avalonia.Remote.Protocol/MetsysBson.cs +++ b/src/Avalonia.Remote.Protocol/MetsysBson.cs @@ -752,7 +752,7 @@ namespace Metsys.Bson if (memberExpression.Expression.NodeType != ExpressionType.Parameter && memberExpression.Expression.NodeType != ExpressionType.Convert) { - throw new ArgumentException(string.Format("Expression '{0}' must resolve to top-level member.", lambdaExpression), nameof(lambdaExpression)); + throw new ArgumentException($"Expression '{lambdaExpression}' must resolve to top-level member.", nameof(lambdaExpression)); } return memberExpression.Member.Name; default: @@ -942,7 +942,7 @@ namespace Metsys.Bson return new ListWrapper(); } } - throw new BsonException(string.Format("Collection of type {0} cannot be deserialized", type.FullName)); + throw new BsonException($"Collection of type {type.FullName} cannot be deserialized"); } public abstract void Add(object value); @@ -1514,7 +1514,7 @@ namespace Metsys.Bson.Configuration result = Visit((MemberExpression)expression.Left); } var index = Expression.Lambda(expression.Right).Compile().DynamicInvoke(); - return result + string.Format("[{0}]", index); + return result + $"[{index}]"; } private string Visit(MemberExpression expression) @@ -1540,7 +1540,7 @@ namespace Metsys.Bson.Configuration if (expression.Method.Name == "get_Item" && expression.Arguments.Count == 1) { var index = Expression.Lambda(expression.Arguments[0]).Compile().DynamicInvoke(); - name += string.Format("[{0}]", index); + name += $"[{index}]"; } return name; } diff --git a/src/Avalonia.X11/X11Structs.cs b/src/Avalonia.X11/X11Structs.cs index 18f860a1a8..86ef7879a5 100644 --- a/src/Avalonia.X11/X11Structs.cs +++ b/src/Avalonia.X11/X11Structs.cs @@ -1109,7 +1109,7 @@ namespace Avalonia.X11 { public override string ToString () { - return string.Format("MotifWmHints Date: Mon, 17 Jul 2023 17:26:19 +0600 Subject: [PATCH 32/59] Special handling for macos dispatcher quirks --- .../Threading/Dispatcher.Queue.cs | 20 +++++++++++++------ .../Threading/Dispatcher.Timers.cs | 2 +- src/Avalonia.Base/Threading/Dispatcher.cs | 3 +++ .../Threading/DispatcherFrame.cs | 8 ++++++-- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs index 829ab4cf87..1644332aea 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Queue.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Queue.cs @@ -9,7 +9,9 @@ public partial class Dispatcher private readonly DispatcherPriorityQueue _queue = new(); private bool _signaled; private bool _explicitBackgroundProcessingRequested; - private const int MaximumTimeProcessingBackgroundJobs = 50; + private const int MaximumInputStarvationTimeInFallbackMode = 50; + private const int MaximumInputStarvationTimeInExplicitProcessingExplicitMode = 50; + private int _maximumInputStarvationTime; void RequestBackgroundProcessing() { @@ -35,8 +37,8 @@ public partial class Dispatcher lock (InstanceLock) { _explicitBackgroundProcessingRequested = false; - ExecuteJobsCore(); } + ExecuteJobsCore(true); } /// @@ -130,10 +132,10 @@ public partial class Dispatcher lock (InstanceLock) _signaled = false; - ExecuteJobsCore(); + ExecuteJobsCore(false); } - void ExecuteJobsCore() + void ExecuteJobsCore(bool fromExplicitBackgroundProcessingCallback) { long? backgroundJobExecutionStartedAt = null; while (true) @@ -151,7 +153,6 @@ public partial class Dispatcher if (job.Priority > DispatcherPriority.Input) { ExecuteJob(job); - backgroundJobExecutionStartedAt = null; } // If platform supports pending input query, ask the platform if we can continue running low priority jobs else if (_pendingInputImpl?.CanQueryPendingInput == true) @@ -164,6 +165,13 @@ public partial class Dispatcher return; } } + // We can't ask if the implementation has pending input, so we should let it to call us back + // Once it thinks that input is handled + else if (_backgroundProcessingImpl != null && !fromExplicitBackgroundProcessingCallback) + { + RequestBackgroundProcessing(); + return; + } // We can't check if there is pending input, but still need to enforce interactivity // so we stop processing background jobs after some timeout and start a timer to continue later else @@ -171,7 +179,7 @@ public partial class Dispatcher if (backgroundJobExecutionStartedAt == null) backgroundJobExecutionStartedAt = Now; - if (Now - backgroundJobExecutionStartedAt.Value > MaximumTimeProcessingBackgroundJobs) + if (Now - backgroundJobExecutionStartedAt.Value > _maximumInputStarvationTime) { _signaled = true; RequestBackgroundProcessing(); diff --git a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs index bb252b7f55..51408daad1 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Timers.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Timers.cs @@ -127,7 +127,7 @@ public partial class Dispatcher if (needToPromoteTimers) PromoteTimers(); if (needToProcessQueue) - ExecuteJobsCore(); + ExecuteJobsCore(false); UpdateOSTimer(); } diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index f257072dc8..46c6699b5a 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -34,6 +34,9 @@ public partial class Dispatcher : IDispatcher _controlledImpl = _impl as IControlledDispatcherImpl; _pendingInputImpl = _impl as IDispatcherImplWithPendingInput; _backgroundProcessingImpl = _impl as IDispatcherImplWithExplicitBackgroundProcessing; + _maximumInputStarvationTime = _backgroundProcessingImpl == null ? + MaximumInputStarvationTimeInFallbackMode : + MaximumInputStarvationTimeInExplicitProcessingExplicitMode; if (_backgroundProcessingImpl != null) _backgroundProcessingImpl.ReadyForBackgroundProcessing += OnReadyForExplicitBackgroundProcessing; } diff --git a/src/Avalonia.Base/Threading/DispatcherFrame.cs b/src/Avalonia.Base/Threading/DispatcherFrame.cs index e826432475..1a80740420 100644 --- a/src/Avalonia.Base/Threading/DispatcherFrame.cs +++ b/src/Avalonia.Base/Threading/DispatcherFrame.cs @@ -38,10 +38,14 @@ public class DispatcherFrame /// for their important criteria to be met. These frames /// should have a timeout associated with them. /// - public DispatcherFrame(bool exitWhenRequested) + public DispatcherFrame(bool exitWhenRequested) : this(Dispatcher.UIThread, exitWhenRequested) { - Dispatcher = Dispatcher.UIThread; Dispatcher.VerifyAccess(); + } + + internal DispatcherFrame(Dispatcher dispatcher, bool exitWhenRequested) + { + Dispatcher = dispatcher; _exitWhenRequested = exitWhenRequested; _continue = true; } From 368de7e79f5bb8d5870b4aea530633a8534a70d5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Jul 2023 13:18:24 +0000 Subject: [PATCH 33/59] clean up; apply only changed text to editable --- .../Avalonia.Android/AndroidInputMethod.cs | 32 +++++++++++++++++-- .../Platform/SkiaPlatform/TopLevelImpl.cs | 16 ++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index ea1958efa6..dea029aaf2 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -114,16 +114,42 @@ namespace Avalonia.Android private void _client_SurroundingTextChanged(object sender, EventArgs e) { var surroundingText = _client.SurroundingText ?? ""; + var editableText = _inputConnection.EditableWrapper.ToString(); - _inputConnection.EditableWrapper.IgnoreChange = true; + if (editableText != surroundingText) + { + _inputConnection.EditableWrapper.IgnoreChange = true; + + var diff = GetDiff(); - _inputConnection.Editable.Replace(0, _inputConnection.Editable.Length(), surroundingText); + _inputConnection.Editable.Replace(diff.index, editableText.Length, diff.diff); - _inputConnection.EditableWrapper.IgnoreChange = false; + _inputConnection.EditableWrapper.IgnoreChange = false; + } var selection = Client.Selection; _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End); + + (int index, string diff) GetDiff() + { + int index = 0; + + var longerLength = Math.Max(surroundingText.Length, editableText.Length); + + for(int i = 0; i < longerLength; i++) + { + if (surroundingText.Length == i || editableText.Length == i || surroundingText[i] != editableText[i]) + { + index = i; + break; + } + } + + var diffString = surroundingText.Substring(index, surroundingText.Length - index); + + return (index, diffString); + } } public void SetCursorRect(Rect rect) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 5d82d099c9..5492742a4c 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -447,6 +447,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform { private readonly AvaloniaInputConnection _inputConnection; + public event EventHandler SelectionChanged; + public EditableWrapper(AvaloniaInputConnection inputConnection) { _inputConnection = inputConnection; @@ -458,8 +460,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { if (!IgnoreChange && start != end) { - var text = tb.SubSequence(0, tb.Length()); - SelectSurroundingTextForDeletion(start, end); } @@ -470,8 +470,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { if (!IgnoreChange && start != end) { - var text = tb.SubSequence(tbstart, tbend); - SelectSurroundingTextForDeletion(start, end); } @@ -482,6 +480,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform { _inputConnection.InputMethod.Client.Selection = new TextSelection(start, end); } + + public override void SetSpan(Java.Lang.Object what, int start, int end, [GeneratedEnum] SpanTypes flags) + { + base.SetSpan(what, start, end, flags); + } } internal class AvaloniaInputConnection : BaseInputConnection @@ -548,11 +551,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText)) { - if(_composingRegion != null) - { - _inputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End); - } - _toplevel.TextInput(committedText); _composingRegion = null; From 117ea16f0ba8d0099cd96875336f82a9e1082030 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Jul 2023 17:24:19 +0000 Subject: [PATCH 34/59] remove redundant selection update --- src/Android/Avalonia.Android/AndroidInputMethod.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index dea029aaf2..1404adbae0 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -127,10 +127,6 @@ namespace Avalonia.Android _inputConnection.EditableWrapper.IgnoreChange = false; } - var selection = Client.Selection; - - _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End); - (int index, string diff) GetDiff() { int index = 0; From 250743d786147434b36c1f584d7741964f3e4975 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 18 Jul 2023 09:40:03 +0200 Subject: [PATCH 35/59] Rework GetNext/PreviousCharacterHit --- src/Avalonia.Base/Media/GlyphRun.cs | 6 +- .../Media/TextFormatting/TextLineImpl.cs | 321 ++++++------------ .../Media/GlyphRunTests.cs | 2 +- .../Media/TextFormatting/TextLineTests.cs | 116 ++++++- 4 files changed, 222 insertions(+), 223 deletions(-) diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 0f70386424..fcb2cec733 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -424,13 +424,13 @@ namespace Avalonia.Media /// public CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit) { + var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); + if (characterHit.TrailingLength != 0) { - return new CharacterHit(characterHit.FirstCharacterIndex); + return previousCharacterHit; } - var previousCharacterHit = FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); - return new CharacterHit(previousCharacterHit.FirstCharacterIndex); } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index ca31d9a6d0..44f53420de 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -9,7 +9,7 @@ namespace Avalonia.Media.TextFormatting internal static Comparer TextBoundsComparer { get; } = Comparer.Create((x, y) => x.Rectangle.Left.CompareTo(y.Rectangle.Left)); - private IReadOnlyList? _indexedTextRuns; + internal IReadOnlyList? _indexedTextRuns; private readonly TextRun[] _textRuns; private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; @@ -512,38 +512,45 @@ namespace Avalonia.Media.TextFormatting /// public override CharacterHit GetNextCaretCharacterHit(CharacterHit characterHit) { - if (_textRuns.Length == 0) + if (_textRuns.Length == 0 || _indexedTextRuns is null) { return new CharacterHit(); } - if (TryFindNextCharacterHit(characterHit, out var nextCharacterHit)) - { - return nextCharacterHit; - } - - var lastTextPosition = FirstTextSourceIndex + Length; + var currentCharacterrHit = characterHit; + var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - // Can't move, we're after the last character - var runIndex = GetRunIndexAtCharacterIndex(lastTextPosition, LogicalDirection.Forward, out var currentPosition); + var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Forward, out var currentPosition); - var currentRun = _textRuns[runIndex]; + var nextCharacterHit = characterHit; switch (currentRun) { case ShapedTextRun shapedRun: { - nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(characterHit); + var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster - characterHit.TrailingLength); + + if (offset > 0) + { + currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength); + } + + nextCharacterHit = shapedRun.GlyphRun.GetNextCaretCharacterHit(currentCharacterrHit); + + if (offset > 0) + { + nextCharacterHit = new CharacterHit(nextCharacterHit.FirstCharacterIndex + offset, nextCharacterHit.TrailingLength); + } break; } - default: + case TextRun: { nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length); break; } } - if (characterHit.FirstCharacterIndex + characterHit.TrailingLength == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength) + if (characterIndex == nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength) { return characterHit; } @@ -554,17 +561,75 @@ namespace Avalonia.Media.TextFormatting /// public override CharacterHit GetPreviousCaretCharacterHit(CharacterHit characterHit) { - if (TryFindPreviousCharacterHit(characterHit, out var previousCharacterHit)) + if (_textRuns.Length == 0 || _indexedTextRuns is null) + { + return new CharacterHit(); + } + + if (characterHit.TrailingLength > 0 && characterHit.FirstCharacterIndex <= FirstTextSourceIndex) + { + return new CharacterHit(FirstTextSourceIndex); + } + + var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; + + if (characterIndex <= FirstTextSourceIndex) { - return previousCharacterHit; + return new CharacterHit(FirstTextSourceIndex); + } + + var currentCharacterrHit = characterHit; + + var currentRun = GetRunAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition); + + if (currentPosition == characterHit.FirstCharacterIndex) + { + currentRun = GetRunAtCharacterIndex(characterHit.FirstCharacterIndex, LogicalDirection.Backward, out currentPosition); + } + + var previousCharacterHit = characterHit; + + switch (currentRun) + { + case ShapedTextRun shapedRun: + { + var offset = Math.Max(0, currentPosition - shapedRun.GlyphRun.Metrics.FirstCluster); + + if (offset > 0) + { + currentCharacterrHit = new CharacterHit(Math.Max(0, characterHit.FirstCharacterIndex - offset), characterHit.TrailingLength); + } + + previousCharacterHit = shapedRun.GlyphRun.GetPreviousCaretCharacterHit(currentCharacterrHit); + + if (offset > 0) + { + previousCharacterHit = new CharacterHit(previousCharacterHit.FirstCharacterIndex + offset, previousCharacterHit.TrailingLength); + } + break; + } + case TextRun: + { + if (characterHit.TrailingLength > 0) + { + previousCharacterHit = new CharacterHit(currentPosition, currentRun.Length); + + } + else + { + previousCharacterHit = new CharacterHit(currentPosition + currentRun.Length); + } + + break; + } } - if (characterHit.FirstCharacterIndex <= FirstTextSourceIndex) + if (characterIndex == previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength) { - characterHit = new CharacterHit(FirstTextSourceIndex); + return characterHit; } - return characterHit; // Can't move, we're before the first character + return previousCharacterHit; } /// @@ -1009,161 +1074,7 @@ namespace Avalonia.Media.TextFormatting if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine) { _textLineBreak = new TextLineBreak(textEndOfLine); - } - } - - /// - /// Tries to find the next character hit. - /// - /// The current character hit. - /// The next character hit. - /// - private bool TryFindNextCharacterHit(CharacterHit characterHit, out CharacterHit nextCharacterHit) - { - nextCharacterHit = characterHit; - - var codepointIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - var lastCodepointIndex = FirstTextSourceIndex + Length; - - if (codepointIndex >= lastCodepointIndex) - { - return false; // Cannot go forward anymore - } - - if (codepointIndex < FirstTextSourceIndex) - { - codepointIndex = FirstTextSourceIndex; - } - - var runIndex = GetRunIndexAtCharacterIndex(codepointIndex, LogicalDirection.Forward, out var currentPosition); - - while (runIndex < _textRuns.Length) - { - var currentRun = _textRuns[runIndex]; - - switch (currentRun) - { - case ShapedTextRun shapedRun: - { - var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex + characterHit.TrailingLength, out _); - - var isAtEnd = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength == FirstTextSourceIndex + Length; - - if (isAtEnd && !shapedRun.GlyphRun.IsLeftToRight) - { - nextCharacterHit = foundCharacterHit; - - return true; - } - - nextCharacterHit = isAtEnd || characterHit.TrailingLength != 0 ? - foundCharacterHit : - new CharacterHit(foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength); - - if (isAtEnd || nextCharacterHit.FirstCharacterIndex > characterHit.FirstCharacterIndex) - { - return true; - } - - break; - } - default: - { - var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - - if (textPosition == currentPosition) - { - nextCharacterHit = new CharacterHit(currentPosition + currentRun.Length); - - return true; - } - - break; - } - } - - currentPosition += currentRun.Length; - runIndex++; - } - - return false; - } - - /// - /// Tries to find the previous character hit. - /// - /// The current character hit. - /// The previous character hit. - /// - private bool TryFindPreviousCharacterHit(CharacterHit characterHit, out CharacterHit previousCharacterHit) - { - var characterIndex = characterHit.FirstCharacterIndex + characterHit.TrailingLength; - - if (characterIndex == FirstTextSourceIndex) - { - previousCharacterHit = new CharacterHit(FirstTextSourceIndex); - - return true; - } - - previousCharacterHit = characterHit; - - if (characterIndex < FirstTextSourceIndex) - { - return false; // Cannot go backward anymore. - } - - var runIndex = GetRunIndexAtCharacterIndex(characterIndex, LogicalDirection.Backward, out var currentPosition); - - while (runIndex >= 0) - { - var currentRun = _textRuns[runIndex]; - - switch (currentRun) - { - case ShapedTextRun shapedRun: - { - var foundCharacterHit = shapedRun.GlyphRun.FindNearestCharacterHit(characterHit.FirstCharacterIndex - 1, out _); - - if (foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength < characterIndex) - { - previousCharacterHit = foundCharacterHit; - - return true; - } - - var previousPosition = foundCharacterHit.FirstCharacterIndex + foundCharacterHit.TrailingLength; - - if (foundCharacterHit.TrailingLength > 0 && previousPosition == characterIndex) - { - previousCharacterHit = new CharacterHit(foundCharacterHit.FirstCharacterIndex); - } - - if (previousCharacterHit != characterHit) - { - return true; - } - - break; - } - default: - { - if (characterIndex == currentPosition + currentRun.Length) - { - previousCharacterHit = new CharacterHit(currentPosition); - - return true; - } - - break; - } - } - - currentPosition -= currentRun.Length; - runIndex--; } - - return false; } /// @@ -1173,15 +1084,23 @@ namespace Avalonia.Media.TextFormatting /// The logical direction. /// The text position of the found run index. /// The text run index. - private int GetRunIndexAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition) + private TextRun? GetRunAtCharacterIndex(int codepointIndex, LogicalDirection direction, out int textPosition) { var runIndex = 0; textPosition = FirstTextSourceIndex; + + if (_indexedTextRuns is null) + { + return null; + } + + TextRun? currentRun = null; TextRun? previousRun = null; - while (runIndex < _textRuns.Length) + while (runIndex < _indexedTextRuns.Count) { - var currentRun = _textRuns[runIndex]; + var indexedRun = _indexedTextRuns[runIndex]; + currentRun = indexedRun.TextRun; switch (currentRun) { @@ -1189,64 +1108,49 @@ namespace Avalonia.Media.TextFormatting { var firstCluster = shapedRun.GlyphRun.Metrics.FirstCluster; - if (firstCluster > codepointIndex) - { - break; - } - - if (previousRun is ShapedTextRun previousShaped && !previousShaped.ShapedBuffer.IsLeftToRight) - { - if (shapedRun.ShapedBuffer.IsLeftToRight) - { - if (firstCluster >= codepointIndex) - { - return --runIndex; - } - } - else - { - if (codepointIndex > firstCluster + currentRun.Length) - { - return --runIndex; - } - } - } + firstCluster += Math.Max(0, indexedRun.TextSourceCharacterIndex - firstCluster); if (direction == LogicalDirection.Forward) { - if (codepointIndex >= firstCluster && codepointIndex <= firstCluster + currentRun.Length) + if (codepointIndex >= firstCluster && codepointIndex < firstCluster + currentRun.Length) { - return runIndex; + return currentRun; } } else { - if (codepointIndex > firstCluster && - codepointIndex <= firstCluster + currentRun.Length) + if (previousRun is not null && previousRun is not ShapedTextRun && codepointIndex == textPosition + firstCluster) + { + textPosition -= previousRun.Length; + + return previousRun; + } + + if (codepointIndex > firstCluster && codepointIndex <= firstCluster + currentRun.Length) { - return runIndex; + return currentRun; } } if (runIndex + 1 >= _textRuns.Length) { - return runIndex; + return currentRun; } textPosition += currentRun.Length; break; } - default: + case TextRun: { if (codepointIndex == textPosition) { - return runIndex; + return currentRun; } if (runIndex + 1 >= _textRuns.Length) { - return runIndex; + return currentRun; } textPosition += currentRun.Length; @@ -1257,10 +1161,11 @@ namespace Avalonia.Media.TextFormatting } runIndex++; + previousRun = currentRun; } - return runIndex; + return currentRun; } private TextLineMetrics CreateLineMetrics() diff --git a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs index c273cc6489..69d7fc4916 100644 --- a/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs +++ b/tests/Avalonia.Base.UnitTests/Media/GlyphRunTests.cs @@ -111,7 +111,7 @@ namespace Avalonia.Base.UnitTests.Media using(UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) using (var glyphRun = CreateGlyphRun(advances, clusters, bidiLevel)) { - var characterHit = glyphRun.GetPreviousCaretCharacterHit(new CharacterHit(currentIndex, currentLength)); + var characterHit = glyphRun.GetPreviousCaretCharacterHit(new CharacterHit(currentIndex + currentLength)); Assert.Equal(previousIndex, characterHit.FirstCharacterIndex); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 12427e1f9e..d576a64523 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -194,7 +194,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting for (var i = 0; i < clusters.Count; i++) { var expectedCluster = clusters[i]; - var actualCluster = nextCharacterHit.FirstCharacterIndex; + var actualCluster = nextCharacterHit.FirstCharacterIndex + nextCharacterHit.TrailingLength; Assert.Equal(expectedCluster, actualCluster); @@ -278,16 +278,6 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(clusters[i], previousCharacterHit.FirstCharacterIndex + previousCharacterHit.TrailingLength); } - - firstCharacterHit = previousCharacterHit; - - firstCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit); - - previousCharacterHit = textLine.GetPreviousCaretCharacterHit(firstCharacterHit); - - Assert.Equal(firstCharacterHit.FirstCharacterIndex, previousCharacterHit.FirstCharacterIndex); - - Assert.Equal(0, previousCharacterHit.TrailingLength); } } @@ -728,6 +718,110 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Should_GetNextCaretCharacterHit_From_Mixed_TextBuffer() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + var textSource = new MixedTextBufferTextSource(); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(9, 1)); + + Assert.Equal(10, characterHit.FirstCharacterIndex); + + Assert.Equal(1, characterHit.TrailingLength); + + characterHit = textLine.GetNextCaretCharacterHit(characterHit); + + Assert.Equal(11, characterHit.FirstCharacterIndex); + + Assert.Equal(1, characterHit.TrailingLength); + + characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(19, 1)); + + Assert.Equal(20, characterHit.FirstCharacterIndex); + + Assert.Equal(1, characterHit.TrailingLength); + + characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(10)); + + Assert.Equal(11, characterHit.FirstCharacterIndex); + + Assert.Equal(0, characterHit.TrailingLength); + + characterHit = textLine.GetNextCaretCharacterHit(characterHit); + + Assert.Equal(12, characterHit.FirstCharacterIndex); + + Assert.Equal(0, characterHit.TrailingLength); + + characterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(20)); + + Assert.Equal(21, characterHit.FirstCharacterIndex); + + Assert.Equal(0, characterHit.TrailingLength); + } + } + + [Fact] + public void Should_GetPreviousCaretCharacterHit_From_Mixed_TextBuffer() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + var textSource = new MixedTextBufferTextSource(); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 0, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(20, 1)); + + Assert.Equal(19, characterHit.FirstCharacterIndex); + + Assert.Equal(1, characterHit.TrailingLength); + + characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(10, 1)); + + Assert.Equal(9, characterHit.FirstCharacterIndex); + + Assert.Equal(1, characterHit.TrailingLength); + + characterHit = textLine.GetPreviousCaretCharacterHit(characterHit); + + Assert.Equal(8, characterHit.FirstCharacterIndex); + + Assert.Equal(1, characterHit.TrailingLength); + + characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(21)); + + Assert.Equal(20, characterHit.FirstCharacterIndex); + + Assert.Equal(0, characterHit.TrailingLength); + + characterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(11)); + + Assert.Equal(10, characterHit.FirstCharacterIndex); + + Assert.Equal(0, characterHit.TrailingLength); + + characterHit = textLine.GetPreviousCaretCharacterHit(characterHit); + + Assert.Equal(9, characterHit.FirstCharacterIndex); + + Assert.Equal(0, characterHit.TrailingLength); + } + } + private class MixedTextBufferTextSource : ITextSource { public TextRun? GetTextRun(int textSourceIndex) From 7c4ce4bca3ef66859f91d5b4c0dea9e5b72b5e6a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 18 Jul 2023 14:45:33 +0600 Subject: [PATCH 36/59] Make dispatcher more usable on non-ui threads --- .../Threading/AvaloniaSynchronizationContext.cs | 7 ++++--- src/Avalonia.Base/Threading/Dispatcher.Invoke.cs | 4 ++-- src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs | 2 +- src/Avalonia.Base/Threading/DispatcherOperation.cs | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs index e0563876bf..1efaa01442 100644 --- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs +++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs @@ -99,14 +99,15 @@ namespace Avalonia.Threading } } - public static RestoreContext Ensure(DispatcherPriority priority) + public static RestoreContext Ensure(DispatcherPriority priority) => Ensure(Dispatcher.UIThread, priority); + public static RestoreContext Ensure(Dispatcher dispatcher, DispatcherPriority priority) { if (Current is AvaloniaSynchronizationContext avaloniaContext && avaloniaContext.Priority == priority) return default; var oldContext = Current; - Dispatcher.UIThread.VerifyAccess(); - SetSynchronizationContext(Dispatcher.UIThread.GetContextWithPriority(priority)); + dispatcher.VerifyAccess(); + SetSynchronizationContext(dispatcher.GetContextWithPriority(priority)); return new RestoreContext(oldContext); } } diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs index 5995a03758..add990bd57 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs @@ -106,7 +106,7 @@ public partial class Dispatcher // call the callback directly. if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess()) { - using (AvaloniaSynchronizationContext.Ensure(priority)) + using (AvaloniaSynchronizationContext.Ensure(this, priority)) callback(); return; } @@ -228,7 +228,7 @@ public partial class Dispatcher // call the callback directly. if (!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess()) { - using (AvaloniaSynchronizationContext.Ensure(priority)) + using (AvaloniaSynchronizationContext.Ensure(this, priority)) return callback(); } diff --git a/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs b/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs index e1833fef2b..4b60ee7479 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.MainLoop.cs @@ -49,7 +49,7 @@ public partial class Dispatcher try { _frames.Push(frame); - using (AvaloniaSynchronizationContext.Ensure(DispatcherPriority.Normal)) + using (AvaloniaSynchronizationContext.Ensure(this, DispatcherPriority.Normal)) frame.Run(_controlledImpl); } finally diff --git a/src/Avalonia.Base/Threading/DispatcherOperation.cs b/src/Avalonia.Base/Threading/DispatcherOperation.cs index 8bd6d3bc01..0008d771c6 100644 --- a/src/Avalonia.Base/Threading/DispatcherOperation.cs +++ b/src/Avalonia.Base/Threading/DispatcherOperation.cs @@ -258,7 +258,7 @@ public class DispatcherOperation try { - using (AvaloniaSynchronizationContext.Ensure(Priority)) + using (AvaloniaSynchronizationContext.Ensure(Dispatcher, Priority)) InvokeCore(); } finally From 2dd92675e3920f666cf8f67fc908b013ddb07669 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 18 Jul 2023 19:27:19 +0200 Subject: [PATCH 37/59] macOS native: fix destructors accessing freed .NET objects --- native/Avalonia.Native/src/OSX/app.mm | 18 ++++++++++++++++++ native/Avalonia.Native/src/OSX/common.h | 1 + native/Avalonia.Native/src/OSX/main.mm | 7 +++++++ src/Avalonia.Native/AvaloniaNativePlatform.cs | 10 ++++++++-- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm index 9cc9fc9523..88cdf4d9de 100644 --- a/native/Avalonia.Native/src/OSX/app.mm +++ b/native/Avalonia.Native/src/OSX/app.mm @@ -2,6 +2,7 @@ #include "AvnString.h" @interface AvnAppDelegate : NSObject -(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events; +-(void) releaseEvents; @end NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular; @@ -15,6 +16,11 @@ ComPtr _events; return self; } +- (void)releaseEvents +{ + _events = nil; +} + - (void)applicationWillFinishLaunching:(NSNotification *)notification { if([[NSApplication sharedApplication] activationPolicy] != AvnDesiredActivationPolicy) @@ -105,6 +111,18 @@ extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDeleg } } +extern void ReleaseAvnAppEvents() +{ + NSApplication* app = [AvnApplication sharedApplication]; + id delegate = [app delegate]; + if ([delegate isMemberOfClass:[AvnAppDelegate class]]) + { + AvnAppDelegate* avnDelegate = delegate; + [avnDelegate releaseEvents]; + [app setDelegate:nil]; + } +} + HRESULT AvnApplicationCommands::HideApp() { START_COM_CALL; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 5cf1b94a2f..672525c64a 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -38,6 +38,7 @@ extern IAvnMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate); +extern void ReleaseAvnAppEvents(); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; extern NSPoint ToNSPoint (AvnPoint p); extern NSRect ToNSRect (AvnRect r); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 1c7e2cf25a..37fd0758e5 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -197,6 +197,13 @@ class AvaloniaNative : public ComSingleObject Date: Wed, 19 Jul 2023 08:04:22 +0000 Subject: [PATCH 38/59] android - use batch edits to defer updates to input manager --- .../Avalonia.Android/AndroidInputMethod.cs | 29 ++++++- .../Platform/SkiaPlatform/TopLevelImpl.cs | 85 +++++++++++-------- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 1404adbae0..60f56805d1 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -18,6 +18,8 @@ namespace Avalonia.Android public bool IsActive { get; } public InputMethodManager IMM { get; } + + void OnBatchEditedEnded(); } enum CustomImeFlags @@ -103,6 +105,13 @@ namespace Avalonia.Android } private void _client_SelectionChanged(object sender, EventArgs e) + { + if (_inputConnection.IsInBatchEdit) + return; + OnSelectionChanged(); + } + + private void OnSelectionChanged() { var selection = Client.Selection; @@ -112,6 +121,22 @@ namespace Avalonia.Android } private void _client_SurroundingTextChanged(object sender, EventArgs e) + { + if (_inputConnection.IsInBatchEdit) + return; + OnSurroundingTextChanged(); + } + + public void OnBatchEditedEnded() + { + if (_inputConnection.IsInBatchEdit) + return; + + OnSurroundingTextChanged(); + OnSelectionChanged(); + } + + private void OnSurroundingTextChanged() { var surroundingText = _client.SurroundingText ?? ""; var editableText = _inputConnection.EditableWrapper.ToString(); @@ -133,7 +158,7 @@ namespace Avalonia.Android var longerLength = Math.Max(surroundingText.Length, editableText.Length); - for(int i = 0; i < longerLength; i++) + for (int i = 0; i < longerLength; i++) { if (surroundingText.Length == i || editableText.Length == i || surroundingText[i] != editableText[i]) { @@ -142,7 +167,7 @@ namespace Avalonia.Android } } - var diffString = surroundingText.Substring(index, surroundingText.Length - index); + var diffString = surroundingText.Substring(index, surroundingText.Length - index); return (index, diffString); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 5492742a4c..26b8837f76 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.Versioning; +using System.Threading; using Android.App; using Android.Content; using Android.Graphics; @@ -454,6 +455,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform _inputConnection = inputConnection; } + public TextSelection CurrentSelection => new TextSelection(Selection.GetSelectionStart(this), Selection.GetSelectionEnd(this)); + public TextSelection CurrentComposition => new TextSelection(BaseInputConnection.GetComposingSpanStart(this), BaseInputConnection.GetComposingSpanEnd(this)); + public bool IgnoreChange { get; set; } public override IEditable Replace(int start, int end, ICharSequence tb) @@ -480,11 +484,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { _inputConnection.InputMethod.Client.Selection = new TextSelection(start, end); } - - public override void SetSpan(Java.Lang.Object what, int start, int end, [GeneratedEnum] SpanTypes flags) - { - base.SetSpan(what, start, end, flags); - } } internal class AvaloniaInputConnection : BaseInputConnection @@ -493,8 +492,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly IAndroidInputMethod _inputMethod; private readonly EditableWrapper _editable; private bool _commitInProgress; - private (int Start, int End)? _composingRegion; - private TextSelection _selection; + private int _batchLevel = 0; public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true) { @@ -513,36 +511,64 @@ namespace Avalonia.Android.Platform.SkiaPlatform public TopLevelImpl Toplevel => _toplevel; + public bool IsInBatchEdit => _batchLevel > 0; + public override bool SetComposingRegion(int start, int end) { - _composingRegion = new(start, end); - return base.SetComposingRegion(start, end); } public override bool SetComposingText(ICharSequence text, int newCursorPosition) { - if(_composingRegion != null) + BeginBatchEdit(); + _editable.IgnoreChange = true; + + try { - // Select the composing region. - InputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End); - } - var compositionText = text.SubSequence(0, text.Length()); + if (_editable.CurrentComposition.Start > -1) + { + // Select the composing region. + InputMethod.Client.Selection = new TextSelection(_editable.CurrentComposition.Start, _editable.CurrentComposition.End); + } + var compositionText = text.SubSequence(0, text.Length()); + + if (_inputMethod.IsActive && !_commitInProgress) + { + if (string.IsNullOrEmpty(compositionText)) + _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); - if (_inputMethod.IsActive && !_commitInProgress) + else + _toplevel.TextInput(compositionText); + } + base.SetComposingText(text, newCursorPosition); + } + finally { - if (string.IsNullOrEmpty(compositionText)) - _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); + _editable.IgnoreChange = false; - else - _toplevel.TextInput(compositionText); + EndBatchEdit(); } return true; } + public override bool BeginBatchEdit() + { + _batchLevel = Interlocked.Increment(ref _batchLevel); + return base.BeginBatchEdit(); + } + + public override bool EndBatchEdit() + { + _batchLevel = Interlocked.Decrement(ref _batchLevel); + + _inputMethod.OnBatchEditedEnded(); + return base.EndBatchEdit(); + } + public override bool CommitText(ICharSequence text, int newCursorPosition) { + BeginBatchEdit(); _commitInProgress = true; var ret = base.CommitText(text, newCursorPosition); @@ -551,22 +577,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText)) { - _toplevel.TextInput(committedText); - - _composingRegion = null; + _toplevel.TextInput(committedText); } _commitInProgress = false; + EndBatchEdit(); return ret; } - public override bool FinishComposingText() - { - _composingRegion = null; - return base.FinishComposingText(); - } - public override bool DeleteSurroundingText(int beforeLength, int afterLength) { if (InputMethod.IsActive) @@ -577,7 +596,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform if (InputMethod.IsActive) { - var selection = _selection; + var selection = _editable.CurrentSelection; InputMethod.Client.Selection = new TextSelection(selection.Start - beforeLength, selection.End + afterLength); @@ -589,12 +608,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform return result; } - public override bool SetSelection(int start, int end) - { - _selection = new TextSelection(start, end); - return base.SetSelection(start, end); - } - public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode) { switch (actionCode) @@ -628,7 +641,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform return null; } - var selection = _selection; + var selection = _editable.CurrentSelection; ExtractedText extract = new ExtractedText { From c5cabca78f18170ea8bd1dfc5fa4891fb4e4c37b Mon Sep 17 00:00:00 2001 From: yankun Date: Wed, 19 Jul 2023 11:22:12 +0200 Subject: [PATCH 39/59] Update Rotate3DTransition.cs Fix flickering after transition has finished. This was originally contained in my previous PR, but was lost. --- src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs index b4a1f02f09..fb8e9488a7 100644 --- a/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs +++ b/src/Avalonia.Base/Animation/Transitions/Rotate3DTransition.cs @@ -89,6 +89,7 @@ public class Rotate3DTransition: PageSlide { Easing = SlideInEasing, Duration = Duration, + FillMode = FillMode.Forward, Children = { CreateKeyFrame(0d, 90d * (forward ? 1 : -1), 1), From e16deecd6a2ee1b521e5528afa0257fa28cfa903 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 19 Jul 2023 12:05:49 +0200 Subject: [PATCH 40/59] Use ProcessExit to cleanup the native resources --- native/Avalonia.Native/src/OSX/main.mm | 3 ++- src/Avalonia.Native/AvaloniaNativePlatform.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 37fd0758e5..3fddb72529 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -198,7 +198,8 @@ class AvaloniaNative : public ComSingleObject Date: Wed, 19 Jul 2023 12:39:01 +0000 Subject: [PATCH 41/59] only handle deletion in surrounding text on the client side --- .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 26b8837f76..fa01cb83e3 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -583,7 +583,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _commitInProgress = false; EndBatchEdit(); - return ret; + return true; } public override bool DeleteSurroundingText(int beforeLength, int afterLength) @@ -592,7 +592,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { EditableWrapper.IgnoreChange = true; } - var result = base.DeleteSurroundingText(beforeLength, afterLength); if (InputMethod.IsActive) { @@ -605,7 +604,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform EditableWrapper.IgnoreChange = true; } - return result; + return true; } public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode) From 5ee76faabdc59f6b3d5e7fc460d5924e204c4915 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 19 Jul 2023 13:24:55 +0000 Subject: [PATCH 42/59] fix no border resize issue --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index 9a7f4b62e3..bdeda9236f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -59,7 +59,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCCALCSIZE: { - if (ToInt32(wParam) == 1 && !HasFullDecorations || _isClientAreaExtended) + if (ToInt32(wParam) == 1 && _isClientAreaExtended) { return IntPtr.Zero; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 044c2cad67..154b20ef84 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1340,14 +1340,12 @@ namespace Avalonia.Win32 if (!_isFullScreenActive) { - var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0; - var margins = new MARGINS { - cyBottomHeight = margin, - cxRightWidth = margin, - cxLeftWidth = margin, - cyTopHeight = margin + cyBottomHeight = 0, + cxRightWidth = 0, + cxLeftWidth = 0, + cyTopHeight = 0 }; DwmExtendFrameIntoClientArea(_hwnd, ref margins); From 1206996c83d7e89f8ac650a85ebd2751c7575bdb Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 19 Jul 2023 13:47:05 +0000 Subject: [PATCH 43/59] disable resize for None decorations --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index bdeda9236f..f62c99ec4a 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -59,7 +59,7 @@ namespace Avalonia.Win32 case WindowsMessage.WM_NCCALCSIZE: { - if (ToInt32(wParam) == 1 && _isClientAreaExtended) + if (ToInt32(wParam) == 1 && _windowProperties.Decorations == SystemDecorations.None || _isClientAreaExtended) { return IntPtr.Zero; } From fde8744175ee25ba5e231fc58a3064e94f03face Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 19 Jul 2023 15:09:53 +0000 Subject: [PATCH 44/59] delete selection when committext has null text --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index fa01cb83e3..3b2a3657b3 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -575,10 +575,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform var committedText = text.SubSequence(0, text.Length()); - if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText)) - { - _toplevel.TextInput(committedText); - } + if (_inputMethod.IsActive) + if (string.IsNullOrEmpty(committedText)) + _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); + else + _toplevel.TextInput(committedText); _commitInProgress = false; EndBatchEdit(); From 515e0843dec9544aa3c6964c4a580aee2ea28b59 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 19 Jul 2023 14:31:48 +0200 Subject: [PATCH 45/59] Handle ScrollContentPresenter extent rounding errors --- samples/Sandbox/MainWindow.axaml | 7 ----- .../Presenters/ScrollContentPresenter.cs | 19 ++++++++++-- .../Presenters/ScrollContentPresenterTests.cs | 31 +++++++++++++++++++ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml b/samples/Sandbox/MainWindow.axaml index f96abcac96..6929f192c7 100644 --- a/samples/Sandbox/MainWindow.axaml +++ b/samples/Sandbox/MainWindow.axaml @@ -1,11 +1,4 @@ - - - - - - - diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index bd3d2b5171..4ff8e7cfef 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -473,18 +473,31 @@ namespace Avalonia.Controls.Presenters } Viewport = finalSize; + Extent = ComputeExtent(finalSize); + _isAnchorElementDirty = true; + return finalSize; + } + + private Size ComputeExtent(Size viewportSize) + { var childMargin = Child!.Margin; + if (Child.UseLayoutRounding) { var scale = LayoutHelper.GetLayoutScale(Child); childMargin = LayoutHelper.RoundLayoutThickness(childMargin, scale, scale); } - Extent = Child!.Bounds.Size.Inflate(childMargin); - _isAnchorElementDirty = true; + var extent = Child!.Bounds.Size.Inflate(childMargin); - return finalSize; + if (MathUtilities.AreClose(extent.Width, viewportSize.Width, LayoutHelper.LayoutEpsilon)) + extent = extent.WithWidth(viewportSize.Width); + + if (MathUtilities.AreClose(extent.Height, viewportSize.Height, LayoutHelper.LayoutEpsilon)) + extent = extent.WithHeight(viewportSize.Height); + + return extent; } private void OnScrollGesture(object? sender, ScrollGestureEventArgs e) diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs index c7ea5c1b69..30628b1af8 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ScrollContentPresenterTests.cs @@ -275,6 +275,37 @@ namespace Avalonia.Controls.UnitTests.Presenters Assert.Equal(new Size(203.2, 203.2), target.Extent); } + [Fact] + public void Extent_Should_Be_Rounded_To_Viewport_When_Close() + { + var root = new TestRoot + { + LayoutScaling = 1.75, + UseLayoutRounding = true + }; + + var target = new ScrollContentPresenter + { + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + Content = new Border + { + Width = 164.57142857142858, + Height = 164.57142857142858, + Margin = new Thickness(6) + } + }; + + root.Child = target; + target.UpdateChild(); + target.Measure(new Size(1000, 1000)); + target.Arrange(new Rect(0, 0, 1000, 1000)); + + Assert.Equal(new Size(176.00000000000003, 176.00000000000003), target.Child!.DesiredSize); + Assert.Equal(new Size(176, 176), target.Viewport); + Assert.Equal(new Size(176, 176), target.Extent); + } + [Fact] public void Extent_Width_Should_Be_Arrange_Width_When_CanScrollHorizontally_False() { From 571f69fbb5d1f403ebb64d316681392a126b5a6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 01:50:35 +0000 Subject: [PATCH 46/59] Bump word-wrap in /src/Browser/Avalonia.Browser/webapp Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4. - [Release notes](https://github.com/jonschlinkert/word-wrap/releases) - [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4) --- updated-dependencies: - dependency-name: word-wrap dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../Avalonia.Browser/webapp/package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Browser/Avalonia.Browser/webapp/package-lock.json b/src/Browser/Avalonia.Browser/webapp/package-lock.json index 12757fd7a0..fd4c6a9539 100644 --- a/src/Browser/Avalonia.Browser/webapp/package-lock.json +++ b/src/Browser/Avalonia.Browser/webapp/package-lock.json @@ -3296,9 +3296,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -5560,9 +5560,9 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", + "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", "dev": true }, "wrappy": { From a54163f1c10fcb5dbaabd8a9b3eae379f5f53a81 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 20 Jul 2023 13:10:50 +0000 Subject: [PATCH 47/59] select current composing region if active when commiting and ensure selection is set when surrounding text changes --- src/Android/Avalonia.Android/AndroidInputMethod.cs | 7 +++++++ .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 60f56805d1..b6c1154455 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -150,6 +150,13 @@ namespace Avalonia.Android _inputConnection.Editable.Replace(diff.index, editableText.Length, diff.diff); _inputConnection.EditableWrapper.IgnoreChange = false; + + if(diff.index == 0) + { + var selection = _client.Selection; + _client.Selection = new TextSelection(selection.Start, 0); + _client.Selection = selection; + } } (int index, string diff) GetDiff() diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 3b2a3657b3..a6e8d10777 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -571,8 +571,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform BeginBatchEdit(); _commitInProgress = true; + var composingRegion = _editable.CurrentComposition; + var ret = base.CommitText(text, newCursorPosition); + if(composingRegion.Start != -1) + { + InputMethod.Client.Selection = composingRegion; + } + var committedText = text.SubSequence(0, text.Length()); if (_inputMethod.IsActive) From 99f1225f57372094353870ceb60837da7cabf73b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 20 Jul 2023 18:28:41 +0200 Subject: [PATCH 48/59] Prevent NRE --- src/Android/Avalonia.Android/AndroidInputMethod.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index b6c1154455..7d5130cf5d 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -138,6 +138,11 @@ namespace Avalonia.Android private void OnSurroundingTextChanged() { + if(_client is null) + { + return; + } + var surroundingText = _client.SurroundingText ?? ""; var editableText = _inputConnection.EditableWrapper.ToString(); From ed6331cd3dfde3036da361cdddc75e5031228e30 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 3 Nov 2021 15:41:53 +0100 Subject: [PATCH 49/59] feat(DataGrid): Allow binding DataGridColumn Width --- .../DataGridColumn.cs | 159 ++++++++++-------- .../DataGridColumnHeader.cs | 4 +- .../DataGridColumns.cs | 17 +- .../DataGridLength.cs | 1 - 4 files changed, 106 insertions(+), 75 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index a5695afeb7..a77ac985ac 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -7,7 +7,6 @@ using Avalonia.Data; using Avalonia.Interactivity; using Avalonia.VisualTree; using Avalonia.Collections; -using Avalonia.Utilities; using System; using System.ComponentModel; using System.Linq; @@ -24,8 +23,6 @@ namespace Avalonia.Controls { internal const int DATAGRIDCOLUMN_maximumWidth = 65536; private const bool DATAGRIDCOLUMN_defaultIsReadOnly = false; - - private DataGridLength? _width; // Null by default, null means inherit the Width from the DataGrid private bool? _isReadOnly; private double? _maxWidth; private double? _minWidth; @@ -39,6 +36,7 @@ namespace Avalonia.Controls private IBinding _clipboardContentBinding; private ControlTheme _cellTheme; private Classes _cellStyleClasses; + private bool _setWidthInternalNoCallback; /// /// Initializes a new instance of the class. @@ -214,6 +212,36 @@ namespace Avalonia.Controls OwningGrid?.OnColumnVisibleStateChanged(this); NotifyPropertyChanged(change.Property.Name); } + else if (change.Property == WidthProperty) + { + if (!_settingWidthInternally) + { + InheritsWidth = false; + } + if (_setWidthInternalNoCallback == false) + { + var grid = OwningGrid; + var width = (change as AvaloniaPropertyChangedEventArgs).NewValue.Value; + if (grid != null) + { + var oldWidth = (change as AvaloniaPropertyChangedEventArgs).OldValue.Value; + if (width.IsStar != oldWidth.IsStar) + { + SetWidthInternalNoCallback(width); + IsInitialDesiredWidthDetermined = false; + grid.OnColumnWidthChanged(this); + } + else + { + Resize(oldWidth, width, false); + } + } + else + { + SetWidthInternalNoCallback(width); + } + } + } } @@ -549,48 +577,15 @@ namespace Avalonia.Controls } } + public static readonly StyledProperty WidthProperty = AvaloniaProperty + .Register(nameof(Width) + , coerce: CoerceWidth + ); + public DataGridLength Width { - get - { - return - _width ?? - OwningGrid?.ColumnWidth ?? - // We don't have a good choice here because we don't want to make this property nullable, see DevDiv Bugs 196581 - DataGridLength.Auto; - } - set - { - if (!_width.HasValue || _width.Value != value) - { - if (!_settingWidthInternally) - { - InheritsWidth = false; - } - - if (OwningGrid != null) - { - DataGridLength width = CoerceWidth(value); - if (width.IsStar != Width.IsStar) - { - // If a column has changed either from or to a star value, we want to recalculate all - // star column widths. They are recalculated during Measure based off what the value we set here. - SetWidthInternalNoCallback(width); - IsInitialDesiredWidthDetermined = false; - OwningGrid.OnColumnWidthChanged(this); - } - else - { - // If a column width's value is simply changing, we resize it (to the right only). - Resize(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue, false); - } - } - else - { - SetWidthInternalNoCallback(value); - } - } - } + get => this.GetValue(WidthProperty); + set => SetValue(WidthProperty, value); } /// @@ -812,19 +807,34 @@ namespace Avalonia.Controls /// on the rest of the star columns. For pixel widths, the desired value is based on the pixel value. /// For auto widths, the desired value is initialized as the column's minimum width. /// + /// /// The DataGridLength to coerce. /// The resultant (coerced) DataGridLength. - internal DataGridLength CoerceWidth(DataGridLength width) + static DataGridLength CoerceWidth(AvaloniaObject source, DataGridLength width) { + var target = (DataGridColumn)source; + + if (target._setWidthInternalNoCallback) + { + return width; + } + + if (!target.IsSet(WidthProperty)) + { + + return target.OwningGrid?.ColumnWidth ?? + DataGridLength.Auto; + } + double desiredValue = width.DesiredValue; if (double.IsNaN(desiredValue)) { - if (width.IsStar && OwningGrid != null && OwningGrid.ColumnsInternal != null) + if (width.IsStar && target.OwningGrid != null && target.OwningGrid.ColumnsInternal != null) { double totalStarValues = 0; double totalStarDesiredValues = 0; double totalNonStarDisplayWidths = 0; - foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetDisplayedColumns(c => c.IsVisible && c != this && !double.IsNaN(c.Width.DesiredValue))) + foreach (DataGridColumn column in target.OwningGrid.ColumnsInternal.GetDisplayedColumns(c => c.IsVisible && c != target && !double.IsNaN(c.Width.DesiredValue))) { if (column.Width.IsStar) { @@ -839,7 +849,7 @@ namespace Avalonia.Controls if (totalStarValues == 0) { // Compute the new star column's desired value based on the available space if there are no other visible star columns - desiredValue = Math.Max(ActualMinWidth, OwningGrid.CellsWidth - totalNonStarDisplayWidths); + desiredValue = Math.Max(target.ActualMinWidth, target.OwningGrid.CellsWidth - totalNonStarDisplayWidths); } else { @@ -853,7 +863,7 @@ namespace Avalonia.Controls } else { - desiredValue = ActualMinWidth; + desiredValue = target.ActualMinWidth; } } @@ -862,7 +872,7 @@ namespace Avalonia.Controls { displayValue = desiredValue; } - displayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue)); + displayValue = Math.Max(target.ActualMinWidth, Math.Min(target.ActualMaxWidth, displayValue)); return new DataGridLength(width.Value, width.UnitType, desiredValue, displayValue); } @@ -896,7 +906,7 @@ namespace Avalonia.Controls }; result[!ContentControl.ContentProperty] = this[!HeaderProperty]; result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty]; - if (OwningGrid.ColumnHeaderTheme is {} columnTheme) + if (OwningGrid.ColumnHeaderTheme is { } columnTheme) { result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.Template); } @@ -909,7 +919,7 @@ namespace Avalonia.Controls /// internal void EnsureWidth() { - SetWidthInternalNoCallback(CoerceWidth(Width)); + SetWidthInternalNoCallback(CoerceWidth(this, Width)); } internal Control GenerateElementInternal(DataGridCell cell, object dataItem) @@ -931,17 +941,17 @@ namespace Avalonia.Controls /// can only decrease in size by the amount that the columns after it can increase in size. /// Likewise, the column can only increase in size if other columns can spare the width. /// - /// The new Value. - /// The new UnitType. - /// The new DesiredValue. - /// The new DisplayValue. + /// with before resize. + /// with after resize. /// Whether or not this resize was initiated by a user action. - internal void Resize(double value, DataGridLengthUnitType unitType, double desiredValue, double displayValue, bool userInitiated) + + // double value, DataGridLengthUnitType unitType, double desiredValue, double displayValue + internal void Resize(DataGridLength oldWidth, DataGridLength newWidth, bool userInitiated) { - double newValue = value; - double newDesiredValue = desiredValue; - double newDisplayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue)); - DataGridLengthUnitType newUnitType = unitType; + double newValue = newWidth.Value; + double newDesiredValue = newWidth.DesiredValue; + double newDisplayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, newWidth.DisplayValue)); + DataGridLengthUnitType newUnitType = newWidth.UnitType; int starColumnsCount = 0; double totalDisplayWidth = 0; @@ -955,11 +965,11 @@ namespace Avalonia.Controls // If we're using star sizing, we can only resize the column as much as the columns to the // right will allow (i.e. until they hit their max or min widths). - if (!hasInfiniteAvailableWidth && (starColumnsCount > 0 || (unitType == DataGridLengthUnitType.Star && Width.IsStar && userInitiated))) + if (!hasInfiniteAvailableWidth && (starColumnsCount > 0 || (newUnitType == DataGridLengthUnitType.Star && newWidth.IsStar && userInitiated))) { - double limitedDisplayValue = Width.DisplayValue; + double limitedDisplayValue = oldWidth.DisplayValue; double availableIncrease = Math.Max(0, OwningGrid.CellsWidth - totalDisplayWidth); - double desiredChange = newDisplayValue - Width.DisplayValue; + double desiredChange = newDisplayValue - oldWidth.DisplayValue; if (desiredChange > availableIncrease) { // The desired change is greater than the amount of available space, @@ -979,7 +989,7 @@ namespace Avalonia.Controls // The desired change is negative, so we need to increase the widths of columns to the right. limitedDisplayValue += desiredChange + OwningGrid.IncreaseColumnWidths(DisplayIndex + 1, -desiredChange, userInitiated); } - if (ActualCanUserResize || (Width.IsStar && !userInitiated)) + if (ActualCanUserResize || (oldWidth.IsStar && !userInitiated)) { newDisplayValue = limitedDisplayValue; } @@ -1002,9 +1012,10 @@ namespace Avalonia.Controls } } - DataGridLength oldWidth = Width; - SetWidthInternalNoCallback(new DataGridLength(Math.Min(double.MaxValue, newValue), newUnitType, newDesiredValue, newDisplayValue)); - if (Width != oldWidth) + newDisplayValue = Math.Min(double.MaxValue, newValue); + newWidth = new DataGridLength(newDisplayValue, newUnitType, newDesiredValue, newDisplayValue); + SetWidthInternalNoCallback(newWidth); + if (newWidth != oldWidth) { OwningGrid.OnColumnWidthChanged(this); } @@ -1052,7 +1063,17 @@ namespace Avalonia.Controls /// The new Width. internal void SetWidthInternalNoCallback(DataGridLength width) { - _width = width; + var originalValue = _setWidthInternalNoCallback; + _setWidthInternalNoCallback = true; + try + { + Width = width; + } + finally + { + _setWidthInternalNoCallback = originalValue; + } + } /// @@ -1122,7 +1143,7 @@ namespace Avalonia.Controls && OwningGrid.DataConnection != null && OwningGrid.DataConnection.SortDescriptions != null) { - if(CustomSortComparer != null) + if (CustomSortComparer != null) { return OwningGrid.DataConnection.SortDescriptions diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 0755864c25..28e8a0ed5e 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -785,7 +785,9 @@ namespace Avalonia.Controls double desiredWidth = _originalWidth + mouseDelta; desiredWidth = Math.Max(_dragColumn.ActualMinWidth, Math.Min(_dragColumn.ActualMaxWidth, desiredWidth)); - _dragColumn.Resize(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth, true); + _dragColumn.Resize(_dragColumn.Width, + new(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth), + true); OwningGrid.UpdateHorizontalOffset(_originalHorizontalOffset); diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs index 4056b78bfe..8dd5bd262b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs @@ -91,7 +91,10 @@ namespace Avalonia.Controls // this column is newly added, we'll just set its display value directly. if (UsesStarSizing && column.IsInitialDesiredWidthDetermined) { - column.Resize(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth, false); + var oldWidth = column.Width; + column.Resize(oldWidth, + new(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth), + false); } else { @@ -142,7 +145,7 @@ namespace Avalonia.Controls { Debug.Assert(dataGridColumn != null); - if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn && + if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn && dataGridBoundColumn.Binding is BindingBase binding) { var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); @@ -359,6 +362,7 @@ namespace Avalonia.Controls if (column.IsVisible && oldValue != column.ActualMaxWidth) { + DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue); if (column.ActualMaxWidth < column.Width.DisplayValue) { // If the maximum width has caused the column to decrease in size, try first to resize @@ -371,7 +375,9 @@ namespace Avalonia.Controls { // If the column was previously limited by its maximum value but has more room now, // attempt to resize the column to its desired width. - column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false); + column.Resize(oldWitdh, + new (column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue), + false); } OnColumnWidthChanged(column); } @@ -388,6 +394,7 @@ namespace Avalonia.Controls if (column.IsVisible && oldValue != column.ActualMinWidth) { + DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue); if (column.ActualMinWidth > column.Width.DisplayValue) { // If the minimum width has caused the column to increase in size, try first to resize @@ -400,7 +407,9 @@ namespace Avalonia.Controls { // If the column was previously limited by its minimum value but but can be smaller now, // attempt to resize the column to its desired width. - column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false); + column.Resize(oldWitdh, + new(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue), + false); } OnColumnWidthChanged(column); } diff --git a/src/Avalonia.Controls.DataGrid/DataGridLength.cs b/src/Avalonia.Controls.DataGrid/DataGridLength.cs index a193352f52..9bff8b2cf5 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridLength.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridLength.cs @@ -7,7 +7,6 @@ using Avalonia.Utilities; using System; using System.ComponentModel; using System.Globalization; -using Avalonia.Controls.Utils; namespace Avalonia.Controls { From a5f615d783aab4765769cf45f7b9ad94bce06875 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 7 Jul 2023 11:19:08 +0200 Subject: [PATCH 50/59] feat(ControlCatalog): Add sample how binding DataGridColumn Width --- .../Models/GDPdLengthConverter.cs | 31 +++++++++++++++++++ .../ControlCatalog/Pages/DataGridPage.xaml | 21 +++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 samples/ControlCatalog/Models/GDPdLengthConverter.cs diff --git a/samples/ControlCatalog/Models/GDPdLengthConverter.cs b/samples/ControlCatalog/Models/GDPdLengthConverter.cs new file mode 100644 index 0000000000..034e664305 --- /dev/null +++ b/samples/ControlCatalog/Models/GDPdLengthConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace ControlCatalog.Models; + +internal class GDPdLengthConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is double d) + { + return new Avalonia.Controls.DataGridLength(d,Avalonia.Controls.DataGridLengthUnitType.Pixel,d,d); + } + else if (value is decimal d2) + { + var dv =System.Convert.ToDouble(d2); + return new Avalonia.Controls.DataGridLength(dv, Avalonia.Controls.DataGridLengthUnitType.Pixel, dv, dv); + } + return value; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is Avalonia.Controls.DataGridLength width) + { + return System.Convert.ToDecimal(width.DisplayValue); + } + return value; + } +} diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 356834832d..88252091c4 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -1,11 +1,14 @@ + + @@ -28,8 +31,18 @@ - + + + + + @@ -38,9 +51,11 @@ - From 0ce9a6ed87559506d401a0c0d041438080a34409 Mon Sep 17 00:00:00 2001 From: mrxx99 Date: Sun, 23 Jul 2023 19:49:05 +0200 Subject: [PATCH 51/59] Fix creating fields for attached properties called name. closes #12283 --- src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs | 6 ++++++ tests/Avalonia.Generators.Tests/Views/NamedControl.xml | 3 ++- tests/Avalonia.Generators.Tests/Views/xNamedControl.xml | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs b/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs index 7ed19eb84c..955df90ddd 100644 --- a/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs +++ b/src/tools/Avalonia.Generators/Common/XamlXNameResolver.cs @@ -38,6 +38,7 @@ internal class XamlXNameResolver : INameResolver, IXamlAstVisitor { if (child is XamlAstXamlPropertyValueNode propertyValueNode && propertyValueNode.Property is XamlAstNamePropertyReference namedProperty && + !IsAttachedProperty(namedProperty) && namedProperty.Name == "Name" && propertyValueNode.Values.Count > 0 && propertyValueNode.Values[0] is XamlAstTextNode text) @@ -89,4 +90,9 @@ internal class XamlXNameResolver : INameResolver, IXamlAstVisitor _ => _defaultFieldModifier }; } + + private static bool IsAttachedProperty(XamlAstNamePropertyReference namedProperty) + { + return !namedProperty.DeclaringType.Equals(namedProperty.TargetType); + } } diff --git a/tests/Avalonia.Generators.Tests/Views/NamedControl.xml b/tests/Avalonia.Generators.Tests/Views/NamedControl.xml index ba1efb247f..0a3c3d3111 100644 --- a/tests/Avalonia.Generators.Tests/Views/NamedControl.xml +++ b/tests/Avalonia.Generators.Tests/Views/NamedControl.xml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sample.App.NamedControl"> - \ No newline at end of file + diff --git a/tests/Avalonia.Generators.Tests/Views/xNamedControl.xml b/tests/Avalonia.Generators.Tests/Views/xNamedControl.xml index 7eab86411c..3dc6df2c7d 100644 --- a/tests/Avalonia.Generators.Tests/Views/xNamedControl.xml +++ b/tests/Avalonia.Generators.Tests/Views/xNamedControl.xml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="Sample.App.xNamedControl"> - \ No newline at end of file + From c834b044966d2536fdc648468f9ce84c94f94c34 Mon Sep 17 00:00:00 2001 From: Luc Date: Mon, 24 Jul 2023 19:03:48 +0300 Subject: [PATCH 52/59] Fix segfault on Linux when typing in a TexBox See https://github.com/AvaloniaUI/Avalonia/issues/12049 --- src/Avalonia.X11/X11Info.cs | 20 +++++++++++++++++--- src/Avalonia.X11/X11Platform.cs | 10 +++------- src/Avalonia.X11/X11Window.Ime.cs | 15 +++++++++++---- src/Avalonia.X11/XLib.cs | 2 +- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.X11/X11Info.cs b/src/Avalonia.X11/X11Info.cs index 17cd6b12c6..890ac0d5ac 100644 --- a/src/Avalonia.X11/X11Info.cs +++ b/src/Avalonia.X11/X11Info.cs @@ -34,7 +34,10 @@ namespace Avalonia.X11 public bool HasXim { get; set; } public bool HasXSync { get; set; } public IntPtr DefaultFontSet { get; set; } - + + [DllImport("libc")] + private static extern void setlocale(int type, string s); + public unsafe X11Info(IntPtr display, IntPtr deferredDisplay, bool useXim) { Display = display; @@ -48,7 +51,10 @@ namespace Avalonia.X11 DefaultFontSet = XCreateFontSet(Display, "-*-*-*-*-*-*-*-*-*-*-*-*-*-*", out var _, out var _, IntPtr.Zero); - + + // We have problems with text input otherwise + setlocale(0, ""); + if (useXim) { XSetLocaleModifiers(""); @@ -59,7 +65,15 @@ namespace Avalonia.X11 if (Xim == IntPtr.Zero) { - XSetLocaleModifiers("@im=none"); + if (XSetLocaleModifiers("@im=none") == IntPtr.Zero) + { + setlocale(0, "en_US.UTF-8"); + if (XSetLocaleModifiers("@im=none") == IntPtr.Zero) + { + setlocale(0, "C.UTF-8"); + XSetLocaleModifiers("@im=none"); + } + } Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 12e4e09158..91c0190e8b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -36,12 +36,11 @@ namespace Avalonia.X11 public IntPtr OrphanedWindow { get; private set; } public X11Globals Globals { get; private set; } public ManualRawEventGrouperDispatchQueue EventGrouperDispatchQueue { get; } = new(); - [DllImport("libc")] - private static extern void setlocale(int type, string s); + public void Initialize(X11PlatformOptions options) { Options = options; - + bool useXim = false; if (EnableIme(options)) { @@ -50,9 +49,6 @@ namespace Avalonia.X11 useXim = true; } - // We have problems with text input otherwise - setlocale(0, ""); - XInitThreads(); Display = XOpenDisplay(IntPtr.Zero); if (Display == IntPtr.Zero) @@ -64,7 +60,7 @@ namespace Avalonia.X11 OrphanedWindow = XCreateSimpleWindow(Display, XDefaultRootWindow(Display), 0, 0, 1, 1, 0, IntPtr.Zero, IntPtr.Zero); XError.Init(); - + Info = new X11Info(Display, DeferredDisplay, useXim); Globals = new X11Globals(this); //TODO: log diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs index 4ee418fd0a..1264a9dc88 100644 --- a/src/Avalonia.X11/X11Window.Ime.cs +++ b/src/Avalonia.X11/X11Window.Ime.cs @@ -133,14 +133,21 @@ namespace Avalonia.X11 { if (ImeBuffer == IntPtr.Zero) ImeBuffer = Marshal.AllocHGlobal(ImeBufferSize); - - var len = Xutf8LookupString(_xic, ref ev, ImeBuffer.ToPointer(), ImeBufferSize, - out _, out var istatus); - var status = (XLookupStatus)istatus; + + IntPtr istatus; + int len; + if(_xic != IntPtr.Zero) + len = Xutf8LookupString(_xic, ref ev, ImeBuffer.ToPointer(), + ImeBufferSize, out _, out istatus); + else + len = XLookupString(ref ev, ImeBuffer.ToPointer(), ImeBufferSize, + out _, out istatus); if (len == 0) return null; + var status = (XLookupStatus)istatus; + string text; if (status == XLookupStatus.XBufferOverflow) return null; diff --git a/src/Avalonia.X11/XLib.cs b/src/Avalonia.X11/XLib.cs index 641adde7e2..d129e4b025 100644 --- a/src/Avalonia.X11/XLib.cs +++ b/src/Avalonia.X11/XLib.cs @@ -468,7 +468,7 @@ namespace Avalonia.X11 public static extern unsafe int XLookupString(ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status); [DllImport (libX11)] - public static extern unsafe int Xutf8LookupString(IntPtr xic, ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out UIntPtr status); + public static extern unsafe int Xutf8LookupString(IntPtr xic, ref XEvent xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status); [DllImport (libX11)] public static extern unsafe int Xutf8LookupString(IntPtr xic, XEvent* xevent, void* buffer, int num_bytes, out IntPtr keysym, out IntPtr status); From 9c94158ae7da372eedc75c96001d056989ba1c8b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 25 Jul 2023 08:38:52 +0200 Subject: [PATCH 53/59] Make sure TextLine.GetCharacterHitFromDistance for mixed buffers --- .../Media/TextFormatting/TextLineImpl.cs | 2 +- .../Media/TextFormatting/TextLineTests.cs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 44f53420de..86c29941e7 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -216,7 +216,7 @@ namespace Avalonia.Media.TextFormatting if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) { - currentPosition = lineLength - lastRun.Length; + currentPosition = FirstTextSourceIndex + Length - lastRun.Length; } return GetRunCharacterHit(lastRun, currentPosition, distance); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index d576a64523..96c592702b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -822,6 +822,26 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Should_GetCharacterHitFromDistance_From_Mixed_TextBuffer() + { + using (Start()) + { + var defaultProperties = new GenericTextRunProperties(Typeface.Default); + var textSource = new MixedTextBufferTextSource(); + + var formatter = new TextFormatterImpl(); + + var textLine = + formatter.FormatLine(textSource, 20, double.PositiveInfinity, + new GenericTextParagraphProperties(defaultProperties)); + + var characterHit = textLine.GetCharacterHitFromDistance(double.PositiveInfinity); + + Assert.Equal(40, characterHit.FirstCharacterIndex + characterHit.TrailingLength); + } + } + private class MixedTextBufferTextSource : ITextSource { public TextRun? GetTextRun(int textSourceIndex) From 1e64c51879118e24caeb967400f8d8d7f0ba8493 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 25 Jul 2023 09:29:58 +0200 Subject: [PATCH 54/59] Adjust test --- .../Media/TextFormatting/TextLineTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 96c592702b..a3bbdc9a7c 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -713,7 +713,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var characterHit = textLine.GetCharacterHitFromDistance(1000); - Assert.Equal(10, characterHit.FirstCharacterIndex); + Assert.Equal(11, characterHit.FirstCharacterIndex); Assert.Equal(1, characterHit.TrailingLength); } } From bd3f097895c840445647090e64413d848a090641 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 25 Jul 2023 13:26:15 +0200 Subject: [PATCH 55/59] Adjust behavior to match what WPF expects --- src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs | 4 ++-- .../Media/TextFormatting/TextLineTests.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 86c29941e7..a2936f1857 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -204,7 +204,7 @@ namespace Avalonia.Media.TextFormatting if (_paragraphProperties.FlowDirection == FlowDirection.RightToLeft) { - currentPosition = Length - firstRun.Length; + currentPosition += lineLength - firstRun.Length; } return GetRunCharacterHit(firstRun, currentPosition, 0); @@ -216,7 +216,7 @@ namespace Avalonia.Media.TextFormatting if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) { - currentPosition = FirstTextSourceIndex + Length - lastRun.Length; + currentPosition += lineLength - lastRun.Length; } return GetRunCharacterHit(lastRun, currentPosition, distance); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index a3bbdc9a7c..96c592702b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -713,7 +713,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var characterHit = textLine.GetCharacterHitFromDistance(1000); - Assert.Equal(11, characterHit.FirstCharacterIndex); + Assert.Equal(10, characterHit.FirstCharacterIndex); Assert.Equal(1, characterHit.TrailingLength); } } From 5d2b9edec35c1a8f159b85ac547979a349855a94 Mon Sep 17 00:00:00 2001 From: daniilpavliuchyk Date: Tue, 25 Jul 2023 19:27:16 +0300 Subject: [PATCH 56/59] Fix window incorrect positioning with window startup location CenterScreen on MacOS --- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index b954bdd4e3..1a4f979ce7 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -151,6 +151,14 @@ HRESULT WindowBaseImpl::Hide() { @autoreleasepool { if (Window != nullptr) { + auto frame = [Window frame]; + + AvnPoint point; + point.X = frame.origin.x; + point.Y = frame.origin.y + frame.size.height; + + lastPositionSet = ConvertPointY(point); + hasPosition = true; [Window orderOut:Window]; } From 1fe2c29901e63340c0faf6a83b223d5bf24c6b59 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 26 Jul 2023 15:48:45 +0600 Subject: [PATCH 57/59] Started to move composition sync logic to the visual itself --- .../Composition/CompositingRenderer.cs | 122 +------------ src/Avalonia.Base/Visual.Composition.cs | 165 ++++++++++++++++++ src/Avalonia.Base/Visual.cs | 28 +-- 3 files changed, 170 insertions(+), 145 deletions(-) create mode 100644 src/Avalonia.Base/Visual.Composition.cs diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 7c5f5d75d5..4db14ba183 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -142,93 +142,6 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester QueueUpdate(); } - private static void SyncChildren(Visual v) - { - //TODO: Optimize by moving that logic to Visual itself - if(v.CompositionVisual == null) - return; - var compositionChildren = v.CompositionVisual.Children; - var visualChildren = (AvaloniaList)v.GetVisualChildren(); - - PooledList<(Visual visual, int index)>? sortedChildren = null; - if (v.HasNonUniformZIndexChildren && visualChildren.Count > 1) - { - sortedChildren = new (visualChildren.Count); - for (var c = 0; c < visualChildren.Count; c++) - sortedChildren.Add((visualChildren[c], c)); - - // Regular Array.Sort is unstable, we need to provide indices as well to avoid reshuffling elements. - sortedChildren.Sort(static (lhs, rhs) => - { - var result = lhs.visual.ZIndex.CompareTo(rhs.visual.ZIndex); - return result == 0 ? lhs.index.CompareTo(rhs.index) : result; - }); - } - - var childVisual = v.ChildCompositionVisual; - - // Check if the current visual somehow got migrated to another compositor - if (childVisual != null && childVisual.Compositor != v.CompositionVisual.Compositor) - childVisual = null; - - var expectedCount = visualChildren.Count; - if (childVisual != null) - expectedCount++; - - if (compositionChildren.Count == expectedCount) - { - bool mismatch = false; - if (sortedChildren != null) - for (var c = 0; c < visualChildren.Count; c++) - { - if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual)) - { - mismatch = true; - break; - } - } - else - for (var c = 0; c < visualChildren.Count; c++) - if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual)) - { - mismatch = true; - break; - } - - if (childVisual != null && - !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual)) - mismatch = true; - - if (!mismatch) - { - sortedChildren?.Dispose(); - return; - } - } - - compositionChildren.Clear(); - if (sortedChildren != null) - { - foreach (var ch in sortedChildren) - { - var compositionChild = ch.visual.CompositionVisual; - if (compositionChild != null) - compositionChildren.Add(compositionChild); - } - sortedChildren.Dispose(); - } - else - foreach (var ch in visualChildren) - { - var compositionChild = ch.CompositionVisual; - if (compositionChild != null) - compositionChildren.Add(compositionChild); - } - - if (childVisual != null) - compositionChildren.Add(childVisual); - } - private void UpdateCore() { _queuedUpdate = false; @@ -238,36 +151,7 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester if(comp == null) continue; - // TODO: Optimize all of that by moving to the Visual itself, so we won't have to recalculate every time - comp.Offset = new (visual.Bounds.Left, visual.Bounds.Top, 0); - comp.Size = new (visual.Bounds.Width, visual.Bounds.Height); - comp.Visible = visual.IsVisible; - comp.Opacity = (float)visual.Opacity; - comp.ClipToBounds = visual.ClipToBounds; - comp.Clip = visual.Clip?.PlatformImpl; - - - if (!Equals(comp.OpacityMask, visual.OpacityMask)) - comp.OpacityMask = visual.OpacityMask?.ToImmutable(); - - if (!comp.Effect.EffectEquals(visual.Effect)) - comp.Effect = visual.Effect?.ToImmutable(); - - comp.RenderOptions = visual.RenderOptions; - - var renderTransform = Matrix.Identity; - - if (visual.HasMirrorTransform) - renderTransform = new Matrix(-1.0, 0.0, 0.0, 1.0, visual.Bounds.Width, 0); - - if (visual.RenderTransform != null) - { - var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); - var offset = Matrix.CreateTranslation(origin); - renderTransform *= (-offset) * visual.RenderTransform.Value * (offset); - } - - comp.TransformMatrix = renderTransform; + visual.SynchronizeCompositionProperties(); try { @@ -279,11 +163,11 @@ internal class CompositingRenderer : IRendererWithCompositor, IHitTester _recorder.Reset(); } - SyncChildren(visual); + visual.SynchronizeCompositionChildVisuals(); } foreach(var v in _recalculateChildren) if (!_dirty.Contains(v)) - SyncChildren(v); + v.SynchronizeCompositionChildVisuals(); _dirty.Clear(); _recalculateChildren.Clear(); CompositionTarget.Size = _root.ClientSize; diff --git a/src/Avalonia.Base/Visual.Composition.cs b/src/Avalonia.Base/Visual.Composition.cs new file mode 100644 index 0000000000..d6a4e3e9be --- /dev/null +++ b/src/Avalonia.Base/Visual.Composition.cs @@ -0,0 +1,165 @@ +using Avalonia.Collections; +using Avalonia.Collections.Pooled; +using Avalonia.Media; +using Avalonia.Rendering.Composition; +using Avalonia.Rendering.Composition.Server; +using Avalonia.VisualTree; + +namespace Avalonia; + +public partial class Visual +{ + internal CompositionDrawListVisual? CompositionVisual { get; private set; } + internal CompositionVisual? ChildCompositionVisual { get; set; } + + + private protected virtual CompositionDrawListVisual CreateCompositionVisual(Compositor compositor) + => new CompositionDrawListVisual(compositor, + new ServerCompositionDrawListVisual(compositor.Server, this), this); + + internal CompositionVisual AttachToCompositor(Compositor compositor) + { + if (CompositionVisual == null || CompositionVisual.Compositor != compositor) + { + CompositionVisual = CreateCompositionVisual(compositor); + } + + return CompositionVisual; + } + + internal virtual void DetachFromCompositor() + { + if (CompositionVisual != null) + { + if (ChildCompositionVisual != null) + CompositionVisual.Children.Remove(ChildCompositionVisual); + + CompositionVisual.DrawList = null; + CompositionVisual = null; + } + } + + internal virtual void SynchronizeCompositionChildVisuals() + { + if(CompositionVisual == null) + return; + var compositionChildren = CompositionVisual.Children; + var visualChildren = (AvaloniaList)VisualChildren; + + PooledList<(Visual visual, int index)>? sortedChildren = null; + if (HasNonUniformZIndexChildren && visualChildren.Count > 1) + { + sortedChildren = new (visualChildren.Count); + for (var c = 0; c < visualChildren.Count; c++) + sortedChildren.Add((visualChildren[c], c)); + + // Regular Array.Sort is unstable, we need to provide indices as well to avoid reshuffling elements. + sortedChildren.Sort(static (lhs, rhs) => + { + var result = lhs.visual.ZIndex.CompareTo(rhs.visual.ZIndex); + return result == 0 ? lhs.index.CompareTo(rhs.index) : result; + }); + } + + var childVisual = ChildCompositionVisual; + + // Check if the current visual somehow got migrated to another compositor + if (childVisual != null && childVisual.Compositor != CompositionVisual.Compositor) + childVisual = null; + + var expectedCount = visualChildren.Count; + if (childVisual != null) + expectedCount++; + + if (compositionChildren.Count == expectedCount) + { + bool mismatch = false; + if (sortedChildren != null) + for (var c = 0; c < visualChildren.Count; c++) + { + if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual)) + { + mismatch = true; + break; + } + } + else + for (var c = 0; c < visualChildren.Count; c++) + if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual)) + { + mismatch = true; + break; + } + + if (childVisual != null && + !ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual)) + mismatch = true; + + if (!mismatch) + { + sortedChildren?.Dispose(); + return; + } + } + + compositionChildren.Clear(); + if (sortedChildren != null) + { + foreach (var ch in sortedChildren) + { + var compositionChild = ch.visual.CompositionVisual; + if (compositionChild != null) + compositionChildren.Add(compositionChild); + } + sortedChildren.Dispose(); + } + else + foreach (var ch in visualChildren) + { + var compositionChild = ch.CompositionVisual; + if (compositionChild != null) + compositionChildren.Add(compositionChild); + } + + if (childVisual != null) + compositionChildren.Add(childVisual); + } + + internal virtual void SynchronizeCompositionProperties() + { + if(CompositionVisual == null) + return; + var comp = CompositionVisual; + + // TODO: Introduce a dirty mask like WPF has, so we don't overwrite properties every time + + comp.Offset = new (Bounds.Left, Bounds.Top, 0); + comp.Size = new (Bounds.Width, Bounds.Height); + comp.Visible = IsVisible; + comp.Opacity = (float)Opacity; + comp.ClipToBounds = ClipToBounds; + comp.Clip = Clip?.PlatformImpl; + + if (!Equals(comp.OpacityMask, OpacityMask)) + comp.OpacityMask = OpacityMask?.ToImmutable(); + + if (!comp.Effect.EffectEquals(Effect)) + comp.Effect = Effect?.ToImmutable(); + + comp.RenderOptions = RenderOptions; + + var renderTransform = Matrix.Identity; + + if (HasMirrorTransform) + renderTransform = new Matrix(-1.0, 0.0, 0.0, 1.0, Bounds.Width, 0); + + if (RenderTransform != null) + { + var origin = RenderTransformOrigin.ToPixels(new Size(Bounds.Width, Bounds.Height)); + var offset = Matrix.CreateTranslation(origin); + renderTransform *= (-offset) * RenderTransform.Value * (offset); + } + + comp.TransformMatrix = renderTransform; + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 1b7975c154..9774468e16 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -29,7 +29,7 @@ namespace Avalonia /// extension methods defined in . /// [UsableDuringInitialization] - public class Visual : StyledElement + public partial class Visual : StyledElement { /// /// Defines the property. @@ -316,9 +316,6 @@ namespace Avalonia /// protected internal IRenderRoot? VisualRoot => _visualRoot ?? (this as IRenderRoot); - internal CompositionDrawListVisual? CompositionVisual { get; private set; } - internal CompositionVisual? ChildCompositionVisual { get; set; } - internal RenderOptions RenderOptions { get; set; } internal bool HasNonUniformZIndexChildren { get; private set; } @@ -515,20 +512,6 @@ namespace Avalonia } } - private protected virtual CompositionDrawListVisual CreateCompositionVisual(Compositor compositor) - => new CompositionDrawListVisual(compositor, - new ServerCompositionDrawListVisual(compositor.Server, this), this); - - internal CompositionVisual AttachToCompositor(Compositor compositor) - { - if (CompositionVisual == null || CompositionVisual.Compositor != compositor) - { - CompositionVisual = CreateCompositionVisual(compositor); - } - - return CompositionVisual; - } - /// /// Calls the method /// for this control and all of its visual descendants. @@ -547,14 +530,7 @@ namespace Avalonia DisableTransitions(); OnDetachedFromVisualTree(e); - if (CompositionVisual != null) - { - if (ChildCompositionVisual != null) - CompositionVisual.Children.Remove(ChildCompositionVisual); - - CompositionVisual.DrawList = null; - CompositionVisual = null; - } + DetachFromCompositor(); DetachedFromVisualTree?.Invoke(this, e); e.Root.Renderer.AddDirty(this); From a99fb216cc12376a99c1d5918a8a813ede2e9373 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Wed, 26 Jul 2023 12:33:58 +0200 Subject: [PATCH 58/59] Use embedded pdb for analyzers and build tasks --- .../Avalonia.Build.Tasks.csproj | 16 +++++++++------- .../Avalonia.Analyzers/Avalonia.Analyzers.csproj | 1 + .../Avalonia.Generators.csproj | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 4f9c7416db..bc36c610c7 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -1,12 +1,14 @@  - netstandard2.0 - exe - false - tools - $(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL - true - $(NoWarn);NU1605;CS8632 + netstandard2.0 + exe + false + tools + $(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL + true + $(NoWarn);NU1605;CS8632 + embedded + false diff --git a/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj b/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj index 05d72618a9..d6538fba13 100644 --- a/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj +++ b/src/tools/Avalonia.Analyzers/Avalonia.Analyzers.csproj @@ -4,6 +4,7 @@ false Avalonia.Analyzers true + embedded true false true diff --git a/src/tools/Avalonia.Generators/Avalonia.Generators.csproj b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj index c6992f9438..a7653562e8 100644 --- a/src/tools/Avalonia.Generators/Avalonia.Generators.csproj +++ b/src/tools/Avalonia.Generators/Avalonia.Generators.csproj @@ -4,6 +4,7 @@ false Avalonia.Generators $(DefineConstants);XAMLX_INTERNAL + embedded true false true From 47988db9c310e4790187d0a2b6d8061ad2195884 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 26 Jul 2023 16:52:18 -0700 Subject: [PATCH 59/59] Update ExternalConsumers.props --- build/ExternalConsumers.props | 1 + 1 file changed, 1 insertion(+) diff --git a/build/ExternalConsumers.props b/build/ExternalConsumers.props index d79e951330..12c747f3f0 100644 --- a/build/ExternalConsumers.props +++ b/build/ExternalConsumers.props @@ -1,6 +1,7 @@ +