From 4da0ae8dab4f844a9d7a4ddd6748c55aa6cee09e Mon Sep 17 00:00:00 2001 From: Arhell Date: Wed, 5 Oct 2022 01:10:26 +0300 Subject: [PATCH 01/11] update test folder links --- NOTICE.md | 4 ++-- .../Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs | 2 +- tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/NOTICE.md b/NOTICE.md index 92fd725957..e97fc654c9 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -111,7 +111,7 @@ DEALINGS IN THE SOFTWARE. # Metsys.Bson -Copyright (c) 2010, Karl Seguin - http://www.openmymind.net/ +Copyright (c) 2010, Karl Seguin - https://www.openmymind.net/ All rights reserved. Redistribution and use in source and binary forms, with or without @@ -302,4 +302,4 @@ https://github.com/chromium/chromium // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 1f0b82b465..26a1ab88c7 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -235,7 +235,7 @@ namespace Avalonia.Base.UnitTests.VisualTree public bool FillContains(Point point) { - // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html + // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html // to determine if the point is in the geometry (since it will always be convex in this situation) for (int i = 0; i < points.Count; i++) { diff --git a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs index 864e2efbaf..9d039a386e 100644 --- a/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs +++ b/tests/Avalonia.UnitTests/MockStreamGeometryImpl.cs @@ -148,7 +148,7 @@ namespace Avalonia.UnitTests public bool FillContains(Point point) { - // Use the algorithm from http://www.blackpawn.com/texts/pointinpoly/default.html + // Use the algorithm from https://www.blackpawn.com/texts/pointinpoly/default.html // to determine if the point is in the geometry (since it will always be convex in this situation) for (int i = 0; i < points.Count; i++) { From ebdb4e1974eeef3900df4cc397217357dedf9571 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 11 Oct 2022 14:31:57 +0000 Subject: [PATCH 02/11] Seperate Avalonia lifecycle from android activity --- .../ControlCatalog.Android/MainActivity.cs | 12 +--- .../ControlCatalog.Android/SplashActivity.cs | 13 +++- .../Avalonia.Android/AvaloniaActivity.cs | 66 +++++-------------- .../AvaloniaSplashActivity.cs | 34 ++++++++++ src/Android/Avalonia.Android/AvaloniaView.cs | 2 +- .../OpenGL/GlPlatformSurface.cs | 6 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 7 -- .../Avalonia.Android/SingleViewLifetime.cs | 26 ++++++++ 8 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 src/Android/Avalonia.Android/AvaloniaSplashActivity.cs create mode 100644 src/Android/Avalonia.Android/SingleViewLifetime.cs diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 33ca511340..3101deb4a9 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,16 +5,8 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaActivity { - protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) - { - return base.CustomizeAppBuilder(builder) - .AfterSetup(_ => - { - Pages.EmbedSample.Implementation = new EmbedSampleAndroid(); - }); - } } } diff --git a/samples/ControlCatalog.Android/SplashActivity.cs b/samples/ControlCatalog.Android/SplashActivity.cs index dc292fd37b..908b5f082a 100644 --- a/samples/ControlCatalog.Android/SplashActivity.cs +++ b/samples/ControlCatalog.Android/SplashActivity.cs @@ -1,12 +1,23 @@ using Android.App; using Android.Content; +using Android.Content.PM; using Android.OS; +using Avalonia.Android; namespace ControlCatalog.Android { [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] - public class SplashActivity : Activity + public class SplashActivity : AvaloniaSplashActivity { + protected override Avalonia.AppBuilder CustomizeAppBuilder(Avalonia.AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .AfterSetup(_ => + { + Pages.EmbedSample.Implementation = new EmbedSampleAndroid(); + }); + } + protected override void OnCreate(Bundle? savedInstanceState) { base.OnCreate(savedInstanceState); diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs index 4ee4bc1375..9d580b2c46 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -1,62 +1,42 @@ +using System; +using Android.App; +using Android.Content; +using Android.Content.Res; using Android.OS; +using Android.Runtime; using AndroidX.AppCompat.App; -using Android.Content.Res; using AndroidX.Lifecycle; -using Avalonia.Controls.ApplicationLifetimes; -using Avalonia.Controls; -using Android.Runtime; -using Android.App; -using Android.Content; -using System; namespace Avalonia.Android { public abstract class AvaloniaActivity : AppCompatActivity { - internal class SingleViewLifetime : ISingleViewApplicationLifetime - { - public AvaloniaView View { get; internal set; } - - public Control MainView - { - get => (Control)View.Content; - set => View.Content = value; - } - } - internal Action ActivityResult; internal AvaloniaView View; internal AvaloniaViewModel _viewModel; - protected abstract AppBuilder CreateAppBuilder(); - protected override void OnCreate(Bundle savedInstanceState) { - var builder = CreateAppBuilder(); + _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; - - var lifetime = new SingleViewLifetime(); - - builder.AfterSetup(x => + View = new AvaloniaView(this); + if (_viewModel.Content != null) { - _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; + View.Content = _viewModel.Content; + } - View = new AvaloniaView(this); - if (_viewModel.Content != null) - { - View.Content = _viewModel.Content; - } + View.Prepare(); - SetContentView(View); + if (Avalonia.Application.Current.ApplicationLifetime is SingleViewLifetime lifetime) + { lifetime.View = View; - - View.Prepare(); - }); - - builder.SetupWithLifetime(lifetime); + } base.OnCreate(savedInstanceState); + + SetContentView(View); } + public object Content { get @@ -90,16 +70,4 @@ namespace Avalonia.Android ActivityResult?.Invoke(requestCode, resultCode, data); } } - - public abstract class AvaloniaActivity : AvaloniaActivity where TApp : Application, new() - { - protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); - - protected override AppBuilder CreateAppBuilder() - { - var builder = AppBuilder.Configure(); - - return CustomizeAppBuilder(builder); - } - } } diff --git a/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs new file mode 100644 index 0000000000..5b5ebd1bd9 --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaSplashActivity.cs @@ -0,0 +1,34 @@ +using Android.OS; +using AndroidX.AppCompat.App; +using AndroidX.Lifecycle; + +namespace Avalonia.Android +{ + public abstract class AvaloniaSplashActivity : AppCompatActivity + { + protected abstract AppBuilder CreateAppBuilder(); + + protected override void OnCreate(Bundle? savedInstanceState) + { + base.OnCreate(savedInstanceState); + + var builder = CreateAppBuilder(); + + var lifetime = new SingleViewLifetime(); + + builder.SetupWithLifetime(lifetime); + } + } + + public abstract class AvaloniaSplashActivity : AvaloniaSplashActivity where TApp : Application, new() + { + protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid(); + + protected override AppBuilder CreateAppBuilder() + { + var builder = AppBuilder.Configure(); + + return CustomizeAppBuilder(builder); + } + } +} diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 94e863210b..34fa121cd7 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -74,7 +74,7 @@ namespace Avalonia.Android class ViewImpl : TopLevelImpl { - public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView) + public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView, true) { View.Focusable = true; View.FocusChange += ViewImpl_FocusChange; diff --git a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs index a9710039f8..e85ed11028 100644 --- a/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs +++ b/src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs @@ -1,4 +1,5 @@ -using Avalonia.OpenGL.Egl; +using Avalonia.OpenGL; +using Avalonia.OpenGL.Egl; using Avalonia.OpenGL.Surfaces; namespace Avalonia.Android.OpenGL @@ -19,7 +20,8 @@ namespace Avalonia.Android.OpenGL public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info) { - if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl) + var feature = AvaloniaLocator.Current.GetService(); + if (feature is EglPlatformOpenGlInterface egl) { return new GlPlatformSurface(egl, info); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index f8eaeba897..1bb74ce897 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -3,20 +3,14 @@ using System.Collections.Generic; using Android.Content; using Android.Graphics; -using Android.Media.TV; -using Android.OS; using Android.Runtime; -using Android.Text; using Android.Views; using Android.Views.InputMethods; -using Android.Widget; using Avalonia.Android.OpenGL; -using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.Specific; using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Android.Platform.Storage; using Avalonia.Controls; -using Avalonia.Controls.Documents; using Avalonia.Controls.Platform; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input; @@ -29,7 +23,6 @@ using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Java.Lang; -using static System.Net.Mime.MediaTypeNames; namespace Avalonia.Android.Platform.SkiaPlatform { diff --git a/src/Android/Avalonia.Android/SingleViewLifetime.cs b/src/Android/Avalonia.Android/SingleViewLifetime.cs new file mode 100644 index 0000000000..eef763a932 --- /dev/null +++ b/src/Android/Avalonia.Android/SingleViewLifetime.cs @@ -0,0 +1,26 @@ +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace Avalonia.Android +{ + internal class SingleViewLifetime : ISingleViewApplicationLifetime + { + private AvaloniaView _view; + + public AvaloniaView View + { + get => _view; internal set + { + if (_view != null) + { + _view.Content = null; + _view.Dispose(); + } + _view = value; + _view.Content = MainView; + } + } + + public Control MainView { get; set; } + } +} From 9f74febca316e5219ee8f2014efb3c18c1935158 Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 11 Oct 2022 21:11:11 -0400 Subject: [PATCH 03/11] Add initial TextBox.TextChanged event --- src/Avalonia.Controls/TextBox.cs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 964a153c8b..76bb37efcf 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Automation.Peers; using System.Diagnostics; +using Avalonia.Threading; namespace Avalonia.Controls { @@ -171,6 +172,10 @@ namespace Avalonia.Controls RoutedEvent.Register( nameof(PastingFromClipboard), RoutingStrategies.Bubble); + public static readonly RoutedEvent TextChangedEvent = + RoutedEvent.Register( + nameof(TextChanged), RoutingStrategies.Bubble); + readonly struct UndoRedoState : IEquatable { public string? Text { get; } @@ -359,8 +364,8 @@ namespace Avalonia.Controls /// public double LineHeight { - get { return GetValue(LineHeightProperty); } - set { SetValue(LineHeightProperty, value); } + get => GetValue(LineHeightProperty); + set => SetValue(LineHeightProperty, value); } [Content] @@ -564,6 +569,18 @@ namespace Avalonia.Controls remove => RemoveHandler(PastingFromClipboardEvent, value); } + /// + /// Occurs when text changes. + /// + /// + /// This event is asynchronous and occurs after the new text is rendered. + /// + public event EventHandler? TextChanged + { + add => AddHandler(TextChangedEvent, value); + remove => RemoveHandler(TextChangedEvent, value); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -1543,7 +1560,16 @@ namespace Avalonia.Controls { if (raiseTextChanged) { - SetAndRaise(TextProperty, ref _text, value); + bool textChanged = SetAndRaise(TextProperty, ref _text, value); + + if (textChanged) + { + Dispatcher.UIThread.Post(() => + { + var eventArgs = new RoutedEventArgs(TextChangedEvent); + RaiseEvent(eventArgs); + }); + } } else { From d3dcf731131c4c8507d1b443e0545ab5b4f83d3c Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 11 Oct 2022 21:43:52 -0400 Subject: [PATCH 04/11] Add TextBox.TextChanging event --- src/Avalonia.Controls/TextBox.cs | 50 +++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 76bb37efcf..eb9f82516e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -160,22 +160,41 @@ namespace Avalonia.Controls (o, v) => o.UndoLimit = v, unsetValue: -1); + /// + /// Defines the event. + /// public static readonly RoutedEvent CopyingToClipboardEvent = RoutedEvent.Register( nameof(CopyingToClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent CuttingToClipboardEvent = RoutedEvent.Register( nameof(CuttingToClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent PastingFromClipboardEvent = RoutedEvent.Register( nameof(PastingFromClipboard), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// public static readonly RoutedEvent TextChangedEvent = RoutedEvent.Register( nameof(TextChanged), RoutingStrategies.Bubble); + /// + /// Defines the event. + /// + public static readonly RoutedEvent TextChangingEvent = + RoutedEvent.Register( + nameof(TextChanging), RoutingStrategies.Bubble); + readonly struct UndoRedoState : IEquatable { public string? Text { get; } @@ -570,17 +589,26 @@ namespace Avalonia.Controls } /// - /// Occurs when text changes. + /// Occurs asynchronously after text changes and the new text is rendered. /// - /// - /// This event is asynchronous and occurs after the new text is rendered. - /// public event EventHandler? TextChanged { add => AddHandler(TextChangedEvent, value); remove => RemoveHandler(TextChangedEvent, value); } + /// + /// Occurs synchronously when text starts to change but before it is rendered. + /// + /// + /// This event occurs just after the property value has been updated. + /// + public event EventHandler? TextChanging + { + add => AddHandler(TextChangingEvent, value); + remove => RemoveHandler(TextChangingEvent, value); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { _presenter = e.NameScope.Get("PART_TextPresenter"); @@ -1564,11 +1592,19 @@ namespace Avalonia.Controls if (textChanged) { + // Note the following sequence of these events (following WinUI) + // 1. TextChanging occurs synchronously when text starts to change but before it is rendered. + // This occurs after the Text property is set. + // 2. TextChanged occurs asynchronously after text changes and the new text is rendered. + + var textChangingEventArgs = new RoutedEventArgs(TextChangingEvent); + RaiseEvent(textChangingEventArgs); + Dispatcher.UIThread.Post(() => { - var eventArgs = new RoutedEventArgs(TextChangedEvent); - RaiseEvent(eventArgs); - }); + var textChangedEventArgs = new RoutedEventArgs(TextChangedEvent); + RaiseEvent(textChangedEventArgs); + }, DispatcherPriority.Normal); } } else From 0869827722b2fea0f3af181fb63b3395ed2ee393 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 12 Oct 2022 07:41:06 +0000 Subject: [PATCH 05/11] Address Review --- samples/ControlCatalog.Android/MainActivity.cs | 4 ++-- samples/MobileSandbox.Android/MainActivity.cs | 4 ++-- samples/MobileSandbox.Android/SplashActivity.cs | 4 ++-- ...valoniaActivity.cs => AvaloniaMainActivity.cs} | 15 +++++++-------- src/Android/Avalonia.Android/AvaloniaViewModel.cs | 11 ----------- .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 +- .../Platform/Storage/AndroidStorageProvider.cs | 4 ++-- 7 files changed, 16 insertions(+), 28 deletions(-) rename src/Android/Avalonia.Android/{AvaloniaActivity.cs => AvaloniaMainActivity.cs} (78%) delete mode 100644 src/Android/Avalonia.Android/AvaloniaViewModel.cs diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 3101deb4a9..62c582610c 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -5,8 +5,8 @@ using Avalonia.Android; namespace ControlCatalog.Android { - [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaMainActivity { } } diff --git a/samples/MobileSandbox.Android/MainActivity.cs b/samples/MobileSandbox.Android/MainActivity.cs index ac9242dd52..d65f0dec92 100644 --- a/samples/MobileSandbox.Android/MainActivity.cs +++ b/samples/MobileSandbox.Android/MainActivity.cs @@ -5,8 +5,8 @@ using Avalonia.Android; namespace MobileSandbox.Android { - [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] - public class MainActivity : AvaloniaActivity + [Activity(Label = "MobileSandbox.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize)] + public class MainActivity : AvaloniaMainActivity { } } diff --git a/samples/MobileSandbox.Android/SplashActivity.cs b/samples/MobileSandbox.Android/SplashActivity.cs index c26371d6fe..ced092554d 100644 --- a/samples/MobileSandbox.Android/SplashActivity.cs +++ b/samples/MobileSandbox.Android/SplashActivity.cs @@ -1,11 +1,11 @@ using Android.App; using Android.Content; -using Android.OS; +using Avalonia.Android; namespace MobileSandbox.Android { [Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)] - public class SplashActivity : Activity + public class SplashActivity : AvaloniaSplashActivity { protected override void OnResume() { diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs similarity index 78% rename from src/Android/Avalonia.Android/AvaloniaActivity.cs rename to src/Android/Avalonia.Android/AvaloniaMainActivity.cs index 9d580b2c46..705fa3c59d 100644 --- a/src/Android/Avalonia.Android/AvaloniaActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -9,20 +9,19 @@ using AndroidX.Lifecycle; namespace Avalonia.Android { - public abstract class AvaloniaActivity : AppCompatActivity + public abstract class AvaloniaMainActivity : AppCompatActivity { + internal static object ViewContent; + internal Action ActivityResult; internal AvaloniaView View; - internal AvaloniaViewModel _viewModel; protected override void OnCreate(Bundle savedInstanceState) { - _viewModel = new ViewModelProvider(this).Get(Java.Lang.Class.FromType(typeof(AvaloniaViewModel))) as AvaloniaViewModel; - View = new AvaloniaView(this); - if (_viewModel.Content != null) + if (ViewContent != null) { - View.Content = _viewModel.Content; + View.Content = ViewContent; } View.Prepare(); @@ -41,11 +40,11 @@ namespace Avalonia.Android { get { - return _viewModel.Content; + return ViewContent; } set { - _viewModel.Content = value; + ViewContent = value; if (View != null) View.Content = value; } diff --git a/src/Android/Avalonia.Android/AvaloniaViewModel.cs b/src/Android/Avalonia.Android/AvaloniaViewModel.cs deleted file mode 100644 index 1b2c00987a..0000000000 --- a/src/Android/Avalonia.Android/AvaloniaViewModel.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace Avalonia.Android -{ - internal class AvaloniaViewModel : AndroidX.Lifecycle.ViewModel - { - public object Content { get; set; } - } -} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1bb74ce897..eb11509bfc 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -52,7 +52,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling); NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); - StorageProvider = new AndroidStorageProvider((AvaloniaActivity)avaloniaView.Context); + StorageProvider = new AndroidStorageProvider((AvaloniaMainActivity)avaloniaView.Context); } public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs index 653f450ec8..3a1a9e76ea 100644 --- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs +++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageProvider.cs @@ -14,10 +14,10 @@ namespace Avalonia.Android.Platform.Storage; internal class AndroidStorageProvider : IStorageProvider { - private readonly AvaloniaActivity _activity; + private readonly AvaloniaMainActivity _activity; private int _lastRequestCode = 20000; - public AndroidStorageProvider(AvaloniaActivity activity) + public AndroidStorageProvider(AvaloniaMainActivity activity) { _activity = activity; } From 591a0794fe43b8ac6dc9dd70b9745d1c3e792906 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 16:39:14 +0200 Subject: [PATCH 06/11] feat: Enable 1825 --- .editorconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.editorconfig b/.editorconfig index 179491eece..a3f524f8b0 100644 --- a/.editorconfig +++ b/.editorconfig @@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true #Net Analyzer dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. +#CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = warning + # Wrapping preferences csharp_wrap_before_ternary_opsigns = false From 543acb1235c6dbed4a7b612b7878214668005f1e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 16:39:38 +0200 Subject: [PATCH 07/11] fix: Address CA1825 rule --- .../Media/TextFormatting/Unicode/GraphemeBreak.cs | 2 +- .../Helpers/ColorPickerHelpers.cs | 2 +- .../Remote/HtmlTransport/SimpleWebSocketHttpServer.cs | 2 +- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 6 +++--- src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs index 6986b908a1..d0ceba62f4 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/GraphemeBreak.cs @@ -2,6 +2,6 @@ namespace Avalonia.Media.TextFormatting.Unicode { internal static class GraphemeBreak { - public static byte[] Data => new byte[0]; + public static byte[] Data => System.Array.Empty(); } } diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index c1904a3c30..381bc42aaa 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -49,7 +49,7 @@ namespace Avalonia.Controls.Primitives { if (width == 0 || height == 0) { - return new byte[0]; + return Array.Empty(); } var bitmap = await Task.Run(() => diff --git a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs index 6af21bbcf5..9a872df960 100644 --- a/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs +++ b/src/Avalonia.DesignerSupport/Remote/HtmlTransport/SimpleWebSocketHttpServer.cs @@ -140,7 +140,7 @@ namespace Avalonia.DesignerSupport.Remote.HtmlTransport IsWebsocketRequest = true; if (headers.TryGetValue("Sec-WebSocket-Protocol", out h)) WebSocketProtocols = h.Split(',').Select(x => x.Trim()).ToArray(); - else WebSocketProtocols = new string[0]; + else WebSocketProtocols = Array.Empty(); } } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index c0511420a6..657e324010 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -340,7 +340,7 @@ namespace Avalonia.FreeDesktop { var id = item == null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); - var children = (depth == 0 || menu == null) ? new object[0] : new object[menu.Items.Count]; + var children = (depth == 0 || menu == null) ? Array.Empty() : new object[menu.Items.Count]; if(menu != null) for (var c = 0; c < children.Length; c++) { @@ -397,7 +397,7 @@ namespace Avalonia.FreeDesktop { foreach (var e in Events) HandleEvent(e.id, e.eventId, e.data, e.timestamp); - return Task.FromResult(new int[0]); + return Task.FromResult(Array.Empty()); } public async Task AboutToShowAsync(int Id) @@ -407,7 +407,7 @@ namespace Avalonia.FreeDesktop public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids) { - return (new int[0], new int[0]); + return (Array.Empty(), Array.Empty()); } #region Events diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index ce210019c0..5c7ec2bbd2 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -99,7 +99,7 @@ namespace Avalonia.LinuxFramebuffer.Output // prepare for the new ioctl call var handles = new uint[] {handle, 0, 0, 0}; var pitches = new uint[] {stride, 0, 0, 0}; - var offsets = new uint[] {}; + var offsets = Array.Empty(); var ret = drmModeAddFB2(_card.Fd, w, h, format, handles, pitches, offsets, out var fbHandle, 0); From 85ac29c788305238e7dd097971bcf71befd8c38e Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 12 Oct 2022 14:58:59 +0200 Subject: [PATCH 08/11] fix: CA1802 --- .editorconfig | 3 +++ src/Windows/Avalonia.Win32/Win32Platform.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 179491eece..917bd21bfe 100644 --- a/.editorconfig +++ b/.editorconfig @@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true #Net Analyzer dotnet_analyzer_diagnostic.category-Performance.severity = none #error - Uncomment when all violations are fixed. +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = warning + # Wrapping preferences csharp_wrap_before_ternary_opsigns = false diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 29b4baeeb0..3cdc3586dc 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -241,8 +241,8 @@ namespace Avalonia.Win32 }); } - private static readonly int SignalW = unchecked((int) 0xdeadbeaf); - private static readonly int SignalL = unchecked((int)0x12345678); + private const int SignalW = unchecked((int)0xdeadbeaf); + private const int SignalL = unchecked((int)0x12345678); public void Signal(DispatcherPriority prio) { From 751474b329908dc9af20cc00c1ddbc847e6e3dda Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 12 Oct 2022 22:40:32 -0400 Subject: [PATCH 09/11] Add TextChangedEventArgs and TextChangingEventArgs --- src/Avalonia.Controls/TextBox.cs | 16 +++++++-------- src/Avalonia.Controls/TextChangedEventArgs.cs | 20 +++++++++++++++++++ .../TextChangingEventArgs.cs | 20 +++++++++++++++++++ 3 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 src/Avalonia.Controls/TextChangedEventArgs.cs create mode 100644 src/Avalonia.Controls/TextChangingEventArgs.cs diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index eb9f82516e..bace6c7811 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -184,15 +184,15 @@ namespace Avalonia.Controls /// /// Defines the event. /// - public static readonly RoutedEvent TextChangedEvent = - RoutedEvent.Register( + public static readonly RoutedEvent TextChangedEvent = + RoutedEvent.Register( nameof(TextChanged), RoutingStrategies.Bubble); /// /// Defines the event. /// - public static readonly RoutedEvent TextChangingEvent = - RoutedEvent.Register( + public static readonly RoutedEvent TextChangingEvent = + RoutedEvent.Register( nameof(TextChanging), RoutingStrategies.Bubble); readonly struct UndoRedoState : IEquatable @@ -591,7 +591,7 @@ namespace Avalonia.Controls /// /// Occurs asynchronously after text changes and the new text is rendered. /// - public event EventHandler? TextChanged + public event EventHandler? TextChanged { add => AddHandler(TextChangedEvent, value); remove => RemoveHandler(TextChangedEvent, value); @@ -603,7 +603,7 @@ namespace Avalonia.Controls /// /// This event occurs just after the property value has been updated. /// - public event EventHandler? TextChanging + public event EventHandler? TextChanging { add => AddHandler(TextChangingEvent, value); remove => RemoveHandler(TextChangingEvent, value); @@ -1597,12 +1597,12 @@ namespace Avalonia.Controls // This occurs after the Text property is set. // 2. TextChanged occurs asynchronously after text changes and the new text is rendered. - var textChangingEventArgs = new RoutedEventArgs(TextChangingEvent); + var textChangingEventArgs = new TextChangingEventArgs(TextChangingEvent); RaiseEvent(textChangingEventArgs); Dispatcher.UIThread.Post(() => { - var textChangedEventArgs = new RoutedEventArgs(TextChangedEvent); + var textChangedEventArgs = new TextChangedEventArgs(TextChangedEvent); RaiseEvent(textChangedEventArgs); }, DispatcherPriority.Normal); } diff --git a/src/Avalonia.Controls/TextChangedEventArgs.cs b/src/Avalonia.Controls/TextChangedEventArgs.cs new file mode 100644 index 0000000000..77c609f19b --- /dev/null +++ b/src/Avalonia.Controls/TextChangedEventArgs.cs @@ -0,0 +1,20 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls +{ + /// + /// Provides data specific to a TextChanged event. + /// + public class TextChangedEventArgs : RoutedEventArgs + { + public TextChangedEventArgs(RoutedEvent? routedEvent) + : base (routedEvent) + { + } + + public TextChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source) + : base(routedEvent, source) + { + } + } +} diff --git a/src/Avalonia.Controls/TextChangingEventArgs.cs b/src/Avalonia.Controls/TextChangingEventArgs.cs new file mode 100644 index 0000000000..4dedbc927b --- /dev/null +++ b/src/Avalonia.Controls/TextChangingEventArgs.cs @@ -0,0 +1,20 @@ +using Avalonia.Interactivity; + +namespace Avalonia.Controls +{ + /// + /// Provides data specific to a TextChanging event. + /// + public class TextChangingEventArgs : RoutedEventArgs + { + public TextChangingEventArgs(RoutedEvent? routedEvent) + : base (routedEvent) + { + } + + public TextChangingEventArgs(RoutedEvent? routedEvent, IInteractive? source) + : base(routedEvent, source) + { + } + } +} From 7f221b321f4b057dd928b3a4f79083fbdaa3c207 Mon Sep 17 00:00:00 2001 From: robloo Date: Wed, 12 Oct 2022 22:49:11 -0400 Subject: [PATCH 10/11] Raise text change events if set through the TextBox.Text property --- src/Avalonia.Controls/TextBox.cs | 49 +++++++++++++------ .../TextBoxTextInputMethodClient.cs | 2 +- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index bace6c7811..da4e90fb66 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -400,11 +400,19 @@ namespace Avalonia.Controls CaretIndex = CoerceCaretIndex(caretIndex, value); SelectionStart = CoerceCaretIndex(selectionStart, value); SelectionEnd = CoerceCaretIndex(selectionEnd, value); - if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) + + var textChanged = SetAndRaise(TextProperty, ref _text, value); + + if (textChanged && IsUndoEnabled && !_isUndoingRedoing) { _undoRedoHelper.Clear(); SnapshotUndoRedo(); // so we always have an initial state } + + if (textChanged) + { + RaiseTextChangeEvents(); + } } } @@ -1297,7 +1305,7 @@ namespace Avalonia.Controls if (text != null && _wordSelectionStart >= 0) { - var distance = caretIndex - _wordSelectionStart; + var distance = caretIndex - _wordSelectionStart; if (distance <= 0) { @@ -1584,6 +1592,29 @@ namespace Avalonia.Controls return text.Substring(start, end - start); } + /// + /// Raises both the and events. + /// + /// + /// This must be called after the property is set. + /// + private void RaiseTextChangeEvents() + { + // Note the following sequence of these events (following WinUI) + // 1. TextChanging occurs synchronously when text starts to change but before it is rendered. + // This occurs after the Text property is set. + // 2. TextChanged occurs asynchronously after text changes and the new text is rendered. + + var textChangingEventArgs = new TextChangingEventArgs(TextChangingEvent); + RaiseEvent(textChangingEventArgs); + + Dispatcher.UIThread.Post(() => + { + var textChangedEventArgs = new TextChangedEventArgs(TextChangedEvent); + RaiseEvent(textChangedEventArgs); + }, DispatcherPriority.Normal); + } + private void SetTextInternal(string value, bool raiseTextChanged = true) { if (raiseTextChanged) @@ -1592,19 +1623,7 @@ namespace Avalonia.Controls if (textChanged) { - // Note the following sequence of these events (following WinUI) - // 1. TextChanging occurs synchronously when text starts to change but before it is rendered. - // This occurs after the Text property is set. - // 2. TextChanged occurs asynchronously after text changes and the new text is rendered. - - var textChangingEventArgs = new TextChangingEventArgs(TextChangingEvent); - RaiseEvent(textChangingEventArgs); - - Dispatcher.UIThread.Post(() => - { - var textChangedEventArgs = new TextChangedEventArgs(TextChangedEvent); - RaiseEvent(textChangedEventArgs); - }, DispatcherPriority.Normal); + RaiseTextChangeEvents(); } } else diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index d39d964277..5d5ffcc381 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls return new TextInputMethodSurroundingText { - Text = lineText ?? "", + Text = lineText ?? "", AnchorOffset = anchorOffset, CursorOffset = cursorOffset }; From d5440ed76b7abcbfb05b2c5a706997aadc68ea49 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 13 Oct 2022 16:48:21 +0000 Subject: [PATCH 11/11] fix android native embedded control regression --- src/Android/Avalonia.Android/AvaloniaView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs index 34fa121cd7..94e863210b 100644 --- a/src/Android/Avalonia.Android/AvaloniaView.cs +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -74,7 +74,7 @@ namespace Avalonia.Android class ViewImpl : TopLevelImpl { - public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView, true) + public ViewImpl(AvaloniaView avaloniaView) : base(avaloniaView) { View.Focusable = true; View.FocusChange += ViewImpl_FocusChange;