Browse Source

Merge pull request #9856 from AvaloniaUI/back_request

Add BackRequested event for TopLevel
pull/9998/head
Max Katz 3 years ago
committed by GitHub
parent
commit
cff78d5678
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 19
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  2. 14
      src/Android/Avalonia.Android/IAndroidNavigationService.cs
  3. 28
      src/Android/Avalonia.Android/Platform/AndroidSystemNavigationManager.cs
  4. 7
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  5. 5
      src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs
  6. 18
      src/Avalonia.Base/Platform/SystemNavigationManager.cs
  7. 62
      src/Avalonia.Controls/TopLevel.cs
  8. 24
      src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs
  9. 5
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  10. 10
      src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs
  11. 5
      src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts
  12. 14
      src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts

19
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<AndroidBackRequestedEventArgs> 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()

14
src/Android/Avalonia.Android/IAndroidNavigationService.cs

@ -0,0 +1,14 @@
using System;
namespace Avalonia.Android
{
public interface IActivityNavigationService
{
event EventHandler<AndroidBackRequestedEventArgs> BackRequested;
}
public class AndroidBackRequestedEventArgs : EventArgs
{
public bool Handled { get; set; }
}
}

28
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<RoutedEventArgs> 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;
}
}
}

7
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -32,7 +32,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;
@ -58,6 +59,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) =>
@ -306,6 +309,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IStorageProvider StorageProvider { get; }
public ISystemNavigationManager SystemNavigationManager { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
if (TransparencyLevel != transparencyLevel)

5
src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs

@ -81,6 +81,10 @@ namespace Avalonia.Input.Platform
{
new KeyGesture(Key.Apps)
};
Back = new List<KeyGesture>
{
new KeyGesture(Key.Left, KeyModifiers.Alt)
};
}
public KeyModifiers CommandModifiers { get; set; }
@ -101,5 +105,6 @@ namespace Avalonia.Input.Platform
public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
public List<KeyGesture> OpenContextMenu { get; set; }
public List<KeyGesture> Back { get; set; }
}
}

18
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<RoutedEventArgs>? BackRequested;
}
}

62
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;
@ -17,6 +18,8 @@ using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using Avalonia.Input.Platform;
using System.Linq;
namespace Avalonia.Controls
{
@ -76,6 +79,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);
/// <summary>
/// Defines the <see cref="BackRequested"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> BackRequestedEvent =
RoutedEvent.Register<TopLevel, RoutedEventArgs>(nameof(BackRequested), RoutingStrategies.Bubble);
private static readonly WeakEvent<IResourceHost, ResourcesChangedEventArgs>
ResourcesChangedWeakEvent = WeakEvent.Register<IResourceHost, ResourcesChangedEventArgs>(
(s, h) => s.ResourcesChanged += h,
@ -89,6 +98,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 +215,48 @@ 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);
};
}
_backGestureSubscription = _inputManager?.PreProcess.Subscribe(e =>
{
bool backRequested = false;
if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>()?.Back;
if (keymap != null)
{
var keyEvent = new KeyEventArgs()
{
KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers,
Key = rawKeyEventArgs.Key
};
backRequested = keymap.Any( key => key.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;
}
});
}
/// <summary>
@ -263,6 +315,15 @@ namespace Avalonia.Controls
set => SetValue(TransparencyBackgroundFallbackProperty, value);
}
/// <summary>
/// Occurs when physical Back Button is pressed or a back navigation has been requested.
/// </summary>
public event EventHandler<RoutedEvent> BackRequested
{
add { AddHandler(BackRequestedEvent, value); }
remove { RemoveHandler(BackRequestedEvent, value); }
}
public ILayoutManager LayoutManager
{
get
@ -382,6 +443,7 @@ namespace Avalonia.Controls
_pointerOverPreProcessor?.OnCompleted();
_pointerOverPreProcessorSubscription?.Dispose();
_backGestureSubscription?.Dispose();
PlatformImpl = null;

24
src/Browser/Avalonia.Browser/BrowserSystemNavigationManager.cs

@ -0,0 +1,24 @@
using System;
using Avalonia.Browser.Interop;
using Avalonia.Interactivity;
using Avalonia.Platform;
namespace Avalonia.Browser
{
internal class BrowserSystemNavigationManager : ISystemNavigationManager
{
public event EventHandler<RoutedEventArgs>? BackRequested;
public BrowserSystemNavigationManager()
{
NavigationHelper.AddBackHandler(() =>
{
var routedEventArgs = new RoutedEventArgs();
BackRequested?.Invoke(this, routedEventArgs);
return routedEventArgs.Handled;
});
}
}
}

5
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -19,7 +19,8 @@ using Avalonia.Rendering.Composition;
namespace Avalonia.Browser
{
internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
internal class BrowserTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider,
ITopLevelWithSystemNavigationManager
{
private Size _clientSize;
private IInputRoot? _inputRoot;
@ -239,5 +240,7 @@ namespace Avalonia.Browser
public INativeControlHostImpl? NativeControlHost { get; }
public IStorageProvider StorageProvider { get; } = new BrowserStorageProvider();
public ISystemNavigationManager SystemNavigationManager { get; } = new BrowserSystemNavigationManager();
}
}

10
src/Browser/Avalonia.Browser/Interop/NavigationHelper.cs

@ -0,0 +1,10 @@
using System;
using System.Runtime.InteropServices.JavaScript;
namespace Avalonia.Browser.Interop;
internal static partial class NavigationHelper
{
[JSImport("NavigationHelper.addBackHandler", AvaloniaModule.MainModuleName)]
public static partial void AddBackHandler([JSMarshalAs<JSType.Function<JSType.Boolean>>] Func<bool> backHandlerCallback);
}

5
src/Browser/Avalonia.Browser/webapp/modules/avalonia.ts

@ -5,6 +5,7 @@ import { AvaloniaDOM } from "./avalonia/dom";
import { Caniuse } from "./avalonia/caniuse";
import { StreamHelper } from "./avalonia/stream";
import { NativeControlHost } from "./avalonia/nativeControlHost";
import { NavigationHelper } from "./avalonia/navigationHelper";
async function registerAvaloniaModule(api: RuntimeAPI): Promise<void> {
api.setModuleImports("avalonia", {
@ -15,7 +16,8 @@ async function registerAvaloniaModule(api: RuntimeAPI): Promise<void> {
DpiWatcher,
AvaloniaDOM,
StreamHelper,
NativeControlHost
NativeControlHost,
NavigationHelper
});
}
export {
@ -27,6 +29,7 @@ export {
AvaloniaDOM,
StreamHelper,
NativeControlHost,
NavigationHelper,
registerAvaloniaModule
};

14
src/Browser/Avalonia.Browser/webapp/modules/avalonia/navigationHelper.ts

@ -0,0 +1,14 @@
export class NavigationHelper {
public static addBackHandler(backHandlerCallback: () => Boolean) {
history.pushState(null, "", window.location.href);
window.onpopstate = () => {
const handled = backHandlerCallback();
if (!handled) {
history.back();
} else {
history.forward();
}
};
}
}
Loading…
Cancel
Save