From 5936f52b2c0cba31d11bf366e74dfedfda299a12 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 3 Jan 2023 13:48:36 +0000 Subject: [PATCH] Add back requested event for TopLevel --- .../Avalonia.Android/AvaloniaMainActivity.cs | 19 ++++--- .../IAndroidNavigationService.cs | 14 +++++ .../AndroidSystemNavigationManager.cs | 28 ++++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 7 ++- .../Platform/SystemNavigationManager.cs | 18 ++++++ src/Avalonia.Controls/TopLevel.cs | 56 +++++++++++++++++++ 6 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 src/Android/Avalonia.Android/IAndroidNavigationService.cs create mode 100644 src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs create mode 100644 src/Avalonia.Base/Platform/SystemNavigationManager.cs diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs index b24581fb8b..c06f5c74ec 100644 --- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs +++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs @@ -1,18 +1,14 @@ using System; using Android.App; using Android.Content; -using Android.Content.Res; using Android.OS; using Android.Runtime; using Android.Views; using AndroidX.AppCompat.App; -using AndroidX.Lifecycle; - -using AndroidRect = Android.Graphics.Rect; namespace Avalonia.Android { - public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler + public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler, IActivityNavigationService { internal static object ViewContent; @@ -56,9 +52,18 @@ namespace Avalonia.Android } } - public override void OnConfigurationChanged(Configuration newConfig) + public event EventHandler BackRequested; + + public override void OnBackPressed() { - base.OnConfigurationChanged(newConfig); + var eventArgs = new AndroidBackRequestedEventArgs(); + + BackRequested?.Invoke(this, eventArgs); + + if (!eventArgs.Handled) + { + base.OnBackPressed(); + } } protected override void OnDestroy() diff --git a/src/Android/Avalonia.Android/IAndroidNavigationService.cs b/src/Android/Avalonia.Android/IAndroidNavigationService.cs new file mode 100644 index 0000000000..5fa93971d9 --- /dev/null +++ b/src/Android/Avalonia.Android/IAndroidNavigationService.cs @@ -0,0 +1,14 @@ +using System; + +namespace Avalonia.Android +{ + public interface IActivityNavigationService + { + event EventHandler BackRequested; + } + + public class AndroidBackRequestedEventArgs : EventArgs + { + public bool Handled { get; set; } + } +} diff --git a/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs new file mode 100644 index 0000000000..bb9dc66f5a --- /dev/null +++ b/src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs @@ -0,0 +1,28 @@ +using System; +using Avalonia.Interactivity; +using Avalonia.Platform; + +namespace Avalonia.Android.Platform +{ + internal class AndroidSystemNavigationManager : ISystemNavigationManager + { + public event EventHandler BackRequested; + + public AndroidSystemNavigationManager(IActivityNavigationService? navigationService) + { + if(navigationService != null) + { + navigationService.BackRequested += OnBackRequested; + } + } + + private void OnBackRequested(object sender, AndroidBackRequestedEventArgs e) + { + var routedEventArgs = new RoutedEventArgs(); + + BackRequested?.Invoke(this, routedEventArgs); + + e.Handled = routedEventArgs.Handled; + } + } +} diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1aac567dda..c504a951e5 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -31,7 +31,8 @@ using Android.Graphics.Drawables; namespace Avalonia.Android.Platform.SkiaPlatform { class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo, - ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider + ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider, + ITopLevelWithSystemNavigationManager { private readonly IGlPlatformSurface _gl; private readonly IFramebufferPlatformSurface _framebuffer; @@ -57,6 +58,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform NativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); StorageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context); + + SystemNavigationManager = new AndroidSystemNavigationManager(avaloniaView.Context as IActivityNavigationService); } public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) => @@ -300,6 +303,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform public IStorageProvider StorageProvider { get; } + public ISystemNavigationManager SystemNavigationManager { get; } + public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { if (TransparencyLevel != transparencyLevel) diff --git a/src/Avalonia.Base/Platform/SystemNavigationManager.cs b/src/Avalonia.Base/Platform/SystemNavigationManager.cs new file mode 100644 index 0000000000..6165b2bb77 --- /dev/null +++ b/src/Avalonia.Base/Platform/SystemNavigationManager.cs @@ -0,0 +1,18 @@ +using System; +using Avalonia.Interactivity; +using Avalonia.Metadata; + +namespace Avalonia.Platform +{ + [Unstable] + public interface ITopLevelWithSystemNavigationManager + { + ISystemNavigationManager SystemNavigationManager { get; } + } + + [Unstable] + public interface ISystemNavigationManager + { + public event EventHandler? BackRequested; + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 9970a0f1c7..66c108b436 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; +using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Logging; using Avalonia.LogicalTree; @@ -76,6 +77,12 @@ namespace Avalonia.Controls public static readonly StyledProperty TransparencyBackgroundFallbackProperty = AvaloniaProperty.Register(nameof(TransparencyBackgroundFallback), Brushes.White); + /// + /// Defines the event. + /// + public static readonly RoutedEvent BackRequestedEvent = + RoutedEvent.Register(nameof(BackRequested), RoutingStrategies.Bubble); + private static readonly WeakEvent ResourcesChangedWeakEvent = WeakEvent.Register( (s, h) => s.ResourcesChanged += h, @@ -89,6 +96,7 @@ namespace Avalonia.Controls private readonly IGlobalStyles? _globalStyles; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; private readonly IDisposable? _pointerOverPreProcessorSubscription; + private readonly IDisposable? _backGestureSubscription; private Size _clientSize; private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; @@ -205,6 +213,44 @@ namespace Avalonia.Controls _pointerOverPreProcessor = new PointerOverPreProcessor(this); _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor); + + if(impl is ITopLevelWithSystemNavigationManager topLevelWithSystemNavigation) + { + topLevelWithSystemNavigation.SystemNavigationManager.BackRequested += (s, e) => + { + e.RoutedEvent = BackRequestedEvent; + RaiseEvent(e); + }; + } + + var backKeyGesture = new KeyGesture(Key.Left, KeyModifiers.Alt); + _backGestureSubscription = _inputManager?.PreProcess.OfType().Subscribe(e => + { + bool backRequested = false; + + if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown) + { + var keyEvent = new KeyEventArgs() + { + KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, + Key = rawKeyEventArgs.Key + }; + + backRequested = backKeyGesture.Matches(keyEvent); + } + else if(e is RawPointerEventArgs pointerEventArgs) + { + backRequested = pointerEventArgs.Type == RawPointerEventType.XButton1Down; + } + + if (backRequested) + { + var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent); + RaiseEvent(backRequestedEventArgs); + + e.Handled = backRequestedEventArgs.Handled; + } + }); } /// @@ -263,6 +309,15 @@ namespace Avalonia.Controls set => SetValue(TransparencyBackgroundFallbackProperty, value); } + /// + /// Occurs when physical Back Button is pressed or a back navigation has been requested. + /// + public event EventHandler BackRequested + { + add { AddHandler(BackRequestedEvent, value); } + remove { RemoveHandler(BackRequestedEvent, value); } + } + public ILayoutManager LayoutManager { get @@ -382,6 +437,7 @@ namespace Avalonia.Controls _pointerOverPreProcessor?.OnCompleted(); _pointerOverPreProcessorSubscription?.Dispose(); + _backGestureSubscription?.Dispose(); PlatformImpl = null;