Browse Source

Raise platform events on Dispatcher, so exceptions in their handlers can be handled by Dispatcher (#14434)

* Raise BackRequested with Send priority

* Raise InsestsManager events with Send priority

* Raise InputPane events with Send priority

* Raise PlatformSettings events with Send priority

* Redirect some more events to the Dispatcher from TopLevel
pull/14736/head
Max Katz 3 years ago
committed by GitHub
parent
commit
d2023a94e8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
  2. 4
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  3. 2
      src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
  4. 15
      src/Avalonia.Controls/Platform/IInputPane.cs
  5. 19
      src/Avalonia.Controls/Platform/IInsetsManager.cs
  6. 15
      src/Avalonia.Controls/TopLevel.cs
  7. 8
      src/Browser/Avalonia.Browser/BrowserInputPane.cs
  8. 14
      src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
  9. 10
      src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs
  10. 15
      src/iOS/Avalonia.iOS/InsetsManager.cs
  11. 12
      src/iOS/Avalonia.iOS/UIKitInputPane.cs

12
src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs

@ -8,6 +8,7 @@ using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Animation.Easings;
using Avalonia.Controls.Platform;
using Avalonia.Media;
using Avalonia.Threading;
using AndroidWindow = Android.Views.Window;
namespace Avalonia.Android.Platform
@ -40,7 +41,7 @@ namespace Avalonia.Android.Platform
if (oldState != value && Build.VERSION.SdkInt <= BuildVersionCodes.Q)
{
var currentRect = OccludedRect;
StateChanged?.Invoke(this, new InputPaneStateEventArgs(value, _previousRect, currentRect, TimeSpan.Zero, null));
NotifyStateChanged(value, _previousRect, currentRect, TimeSpan.Zero, null);
_previousRect = currentRect;
}
}
@ -151,7 +152,12 @@ namespace Avalonia.Android.Platform
private void NotifySafeAreaChanged(Thickness safeAreaPadding)
{
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding));
Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding)));
}
private void NotifyStateChanged(InputPaneState newState, Rect? startRect, Rect endRect, TimeSpan animationDuration, IEasing easing)
{
Dispatcher.UIThread.Send(_ => StateChanged?.Invoke(this, new InputPaneStateEventArgs(newState, startRect, endRect, animationDuration, easing)));
}
public void OnGlobalLayout()
@ -294,7 +300,7 @@ namespace Avalonia.Android.Platform
var duration = TimeSpan.FromMilliseconds(animation.DurationMillis);
bool isOpening = State == InputPaneState.Open;
StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, isOpening ? upperRect : lowerRect, isOpening ? lowerRect : upperRect, duration, new AnimationEasing(animation.Interpolator)));
NotifyStateChanged(State, isOpening ? upperRect : lowerRect, isOpening ? lowerRect : upperRect, duration, new AnimationEasing(animation.Interpolator));
}
}

4
src/Avalonia.Base/Platform/DefaultPlatformSettings.cs

@ -3,6 +3,7 @@ using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Platform
@ -50,7 +51,8 @@ namespace Avalonia.Platform
protected void OnColorValuesChanged(PlatformColorValues colorValues)
{
ColorValuesChanged?.Invoke(this, colorValues);
Dispatcher.UIThread.Send(
_ => ColorValuesChanged?.Invoke(this, colorValues));
}
}
}

2
src/Avalonia.Base/Threading/Dispatcher.Invoke.cs

@ -635,7 +635,7 @@ public partial class Dispatcher
/// <remarks>
/// When on the same thread with Send priority, callback is executed immediately, without changing synchronization context.
/// </remarks>
internal void Send(SendOrPostCallback action, object? arg, DispatcherPriority? priority = null)
internal void Send(SendOrPostCallback action, object? arg = null, DispatcherPriority? priority = null)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
priority ??= DispatcherPriority.Send;

15
src/Avalonia.Controls/Platform/IInputPane.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.Animation.Easings;
using Avalonia.Metadata;
using Avalonia.Threading;
namespace Avalonia.Controls.Platform
{
@ -26,6 +27,20 @@ namespace Avalonia.Controls.Platform
event EventHandler<InputPaneStateEventArgs>? StateChanged;
}
[PrivateApi]
public abstract class InputPaneBase : IInputPane
{
public virtual InputPaneState State { get; protected set; }
public virtual Rect OccludedRect { get; protected set; }
public event EventHandler<InputPaneStateEventArgs>? StateChanged;
protected void OnStateChanged(InputPaneStateEventArgs eventArgs)
{
Dispatcher.UIThread.Send(
_ => StateChanged?.Invoke(this, eventArgs));
}
}
/// <summary>
/// The input pane opened state.
/// </summary>

19
src/Avalonia.Controls/Platform/IInsetsManager.cs

@ -1,11 +1,11 @@
using System;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Threading;
#nullable enable
namespace Avalonia.Controls.Platform
{
[Unstable]
[NotClientImplementable]
public interface IInsetsManager
{
@ -34,6 +34,23 @@ namespace Avalonia.Controls.Platform
/// </summary>
event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
}
[PrivateApi]
public abstract class InsetsManagerBase : IInsetsManager
{
public virtual bool? IsSystemBarVisible { get; set; }
public virtual bool DisplayEdgeToEdge { get; set; }
public virtual Thickness SafeAreaPadding { get; protected set; }
public virtual Color? SystemBarColor { get; set; }
public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
protected void OnSafeAreaChanged(SafeAreaChangedArgs eventArgs)
{
Dispatcher.UIThread.Send(
_ => SafeAreaChanged?.Invoke(this, eventArgs),
null, DispatcherPriority.Send);
}
}
public class SafeAreaChangedArgs : EventArgs
{

15
src/Avalonia.Controls/TopLevel.cs

@ -272,7 +272,7 @@ namespace Avalonia.Controls
systemNavigationManager.BackRequested += (_, e) =>
{
e.RoutedEvent = BackRequestedEvent;
RaiseEvent(e);
Dispatcher.UIThread.Send(_ => RaiseEvent(e));
};
}
@ -306,7 +306,7 @@ namespace Avalonia.Controls
if (backRequested)
{
var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent);
RaiseEvent(backRequestedEventArgs);
Dispatcher.UIThread.Send(_ => RaiseEvent(backRequestedEventArgs));
e.Handled = backRequestedEventArgs.Handled;
}
@ -739,7 +739,7 @@ namespace Avalonia.Controls
private void HandleScalingChanged(double scaling)
{
LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
ScalingChanged?.Invoke(this, EventArgs.Empty);
Dispatcher.UIThread.Send(_ => ScalingChanged?.Invoke(this, EventArgs.Empty));
}
private void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel)
@ -786,14 +786,17 @@ namespace Avalonia.Controls
protected virtual void OnOpened(EventArgs e)
{
FrameSize = PlatformImpl?.FrameSize;
Opened?.Invoke(this, e);
}
Dispatcher.UIThread.Send(_ => Opened?.Invoke(this, e));
}
/// <summary>
/// Raises the <see cref="Closed"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e);
protected virtual void OnClosed(EventArgs e)
{
Dispatcher.UIThread.Send(_ => Closed?.Invoke(this, e));
}
/// <summary>
/// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a

8
src/Browser/Avalonia.Browser/BrowserInputPane.cs

@ -5,17 +5,13 @@ using Avalonia.Controls.Platform;
namespace Avalonia.Browser;
internal class BrowserInputPane : IInputPane
internal class BrowserInputPane : InputPaneBase
{
public BrowserInputPane(JSObject container)
{
InputHelper.SubscribeKeyboardGeometryChange(container, OnGeometryChange);
}
public InputPaneState State { get; private set; }
public Rect OccludedRect { get; private set; }
public event EventHandler<InputPaneStateEventArgs>? StateChanged;
private bool OnGeometryChange(JSObject args)
{
var oldState = (OccludedRect, State);
@ -29,7 +25,7 @@ internal class BrowserInputPane : IInputPane
if (oldState != (OccludedRect, State))
{
StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, null, OccludedRect));
OnStateChanged(new InputPaneStateEventArgs(State, null, OccludedRect));
}
return true;

14
src/Browser/Avalonia.Browser/BrowserInsetsManager.cs

@ -10,9 +10,9 @@ using static Avalonia.Controls.Platform.IInsetsManager;
namespace Avalonia.Browser
{
internal class BrowserInsetsManager : IInsetsManager
internal class BrowserInsetsManager : InsetsManagerBase
{
public bool? IsSystemBarVisible
public override bool? IsSystemBarVisible
{
get
{
@ -24,11 +24,9 @@ namespace Avalonia.Browser
}
}
public bool DisplayEdgeToEdge { get; set; }
public override bool DisplayEdgeToEdge { get; set; }
public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
public Thickness SafeAreaPadding
public override Thickness SafeAreaPadding
{
get
{
@ -38,11 +36,11 @@ namespace Avalonia.Browser
}
}
public Color? SystemBarColor { get; set; }
public override Color? SystemBarColor { get; set; }
public void NotifySafeAreaPaddingChanged()
{
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
OnSafeAreaChanged(new SafeAreaChangedArgs(SafeAreaPadding));
}
}
}

10
src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs

@ -8,7 +8,7 @@ using MicroCom.Runtime;
namespace Avalonia.Win32.Input;
internal unsafe class WindowsInputPane : IInputPane, IDisposable
internal unsafe class WindowsInputPane : InputPaneBase, IDisposable
{
private static readonly Lazy<bool> s_inputPaneSupported = new(() =>
WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.InputPane"));
@ -48,12 +48,6 @@ internal unsafe class WindowsInputPane : IInputPane, IDisposable
return null;
}
public InputPaneState State { get; private set; }
public Rect OccludedRect { get; private set; }
public event EventHandler<InputPaneStateEventArgs>? StateChanged;
private void OnStateChanged(bool showing, UnmanagedMethods.RECT? prcInputPaneScreenLocation)
{
@ -65,7 +59,7 @@ internal unsafe class WindowsInputPane : IInputPane, IDisposable
if (oldState != (OccludedRect, State))
{
StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, null, OccludedRect));
OnStateChanged(new InputPaneStateEventArgs(State, null, OccludedRect));
}
}

15
src/iOS/Avalonia.iOS/InsetsManager.cs

@ -4,7 +4,7 @@ using Avalonia.Media;
namespace Avalonia.iOS;
internal class InsetsManager : IInsetsManager
internal class InsetsManager : InsetsManagerBase
{
private IAvaloniaViewController? _controller;
private bool _displayEdgeToEdge = true;
@ -16,13 +16,13 @@ internal class InsetsManager : IInsetsManager
{
_controller.SafeAreaPaddingChanged += (_, _) =>
{
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
OnSafeAreaChanged(new SafeAreaChangedArgs(SafeAreaPadding));
DisplayEdgeToEdgeChanged?.Invoke(this, _displayEdgeToEdge);
};
}
}
public bool? IsSystemBarVisible
public override bool? IsSystemBarVisible
{
get => _controller?.PrefersStatusBarHidden == false;
set
@ -33,10 +33,9 @@ internal class InsetsManager : IInsetsManager
}
}
}
public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
public event EventHandler<bool>? DisplayEdgeToEdgeChanged;
public bool DisplayEdgeToEdge
public override bool DisplayEdgeToEdge
{
get => _displayEdgeToEdge;
set
@ -45,12 +44,12 @@ internal class InsetsManager : IInsetsManager
{
_displayEdgeToEdge = value;
DisplayEdgeToEdgeChanged?.Invoke(this, value);
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
OnSafeAreaChanged(new SafeAreaChangedArgs(SafeAreaPadding));
}
}
}
public Thickness SafeAreaPadding => _displayEdgeToEdge ? _controller?.SafeAreaPadding ?? default : default;
public override Thickness SafeAreaPadding => _displayEdgeToEdge ? _controller?.SafeAreaPadding ?? default : default;
public Color? SystemBarColor { get; set; }
public override Color? SystemBarColor { get; set; }
}

12
src/iOS/Avalonia.iOS/UIKitInputPane.cs

@ -11,7 +11,7 @@ namespace Avalonia.iOS;
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
[SupportedOSPlatform("ios")]
internal sealed class UIKitInputPane : IInputPane
internal sealed class UIKitInputPane : InputPaneBase
{
public static UIKitInputPane Instance { get; } = new();
@ -24,11 +24,7 @@ internal sealed class UIKitInputPane : IInputPane
.DefaultCenter
.AddObserver(UIKeyboard.WillHideNotification, KeyboardDownNotification);
}
public InputPaneState State { get; private set; }
public Rect OccludedRect { get; private set; }
public event EventHandler<InputPaneStateEventArgs>? StateChanged;
private void KeyboardDownNotification(NSNotification obj) => RaiseEventFromNotification(false, obj);
private void KeyboardUpNotification(NSNotification obj) => RaiseEventFromNotification(true, obj);
@ -38,7 +34,7 @@ internal sealed class UIKitInputPane : IInputPane
State = isUp ? InputPaneState.Open : InputPaneState.Closed;
#if MACCATALYST
OccludedRect = default;
StateChanged?.Invoke(this, new InputPaneStateEventArgs(
OnStateChanged(new InputPaneStateEventArgs(
State, null, OccludedRect));
#else
var startFrame = UIKeyboard.FrameBeginFromNotification(notification);
@ -55,7 +51,7 @@ internal sealed class UIKitInputPane : IInputPane
var startRect = new Rect(startFrame.X, startFrame.Y, startFrame.Width, startFrame.Height);
OccludedRect = new Rect(endFrame.X, endFrame.Y, endFrame.Width, endFrame.Height);
StateChanged?.Invoke(this, new InputPaneStateEventArgs(
OnStateChanged(new InputPaneStateEventArgs(
State, startRect, OccludedRect, TimeSpan.FromSeconds(duration), easing));
#endif
}

Loading…
Cancel
Save