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.Animation.Easings;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Threading;
using AndroidWindow = Android.Views.Window; using AndroidWindow = Android.Views.Window;
namespace Avalonia.Android.Platform namespace Avalonia.Android.Platform
@ -40,7 +41,7 @@ namespace Avalonia.Android.Platform
if (oldState != value && Build.VERSION.SdkInt <= BuildVersionCodes.Q) if (oldState != value && Build.VERSION.SdkInt <= BuildVersionCodes.Q)
{ {
var currentRect = OccludedRect; var currentRect = OccludedRect;
StateChanged?.Invoke(this, new InputPaneStateEventArgs(value, _previousRect, currentRect, TimeSpan.Zero, null)); NotifyStateChanged(value, _previousRect, currentRect, TimeSpan.Zero, null);
_previousRect = currentRect; _previousRect = currentRect;
} }
} }
@ -151,7 +152,12 @@ namespace Avalonia.Android.Platform
private void NotifySafeAreaChanged(Thickness safeAreaPadding) 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() public void OnGlobalLayout()
@ -294,7 +300,7 @@ namespace Avalonia.Android.Platform
var duration = TimeSpan.FromMilliseconds(animation.DurationMillis); var duration = TimeSpan.FromMilliseconds(animation.DurationMillis);
bool isOpening = State == InputPaneState.Open; 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.Input.Platform;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Threading;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Platform namespace Avalonia.Platform
@ -50,7 +51,8 @@ namespace Avalonia.Platform
protected void OnColorValuesChanged(PlatformColorValues colorValues) 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> /// <remarks>
/// When on the same thread with Send priority, callback is executed immediately, without changing synchronization context. /// When on the same thread with Send priority, callback is executed immediately, without changing synchronization context.
/// </remarks> /// </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)); _ = action ?? throw new ArgumentNullException(nameof(action));
priority ??= DispatcherPriority.Send; priority ??= DispatcherPriority.Send;

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

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Threading;
namespace Avalonia.Controls.Platform namespace Avalonia.Controls.Platform
{ {
@ -26,6 +27,20 @@ namespace Avalonia.Controls.Platform
event EventHandler<InputPaneStateEventArgs>? StateChanged; 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> /// <summary>
/// The input pane opened state. /// The input pane opened state.
/// </summary> /// </summary>

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

@ -1,11 +1,11 @@
using System; using System;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Threading;
#nullable enable #nullable enable
namespace Avalonia.Controls.Platform namespace Avalonia.Controls.Platform
{ {
[Unstable]
[NotClientImplementable] [NotClientImplementable]
public interface IInsetsManager public interface IInsetsManager
{ {
@ -34,6 +34,23 @@ namespace Avalonia.Controls.Platform
/// </summary> /// </summary>
event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged; 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 public class SafeAreaChangedArgs : EventArgs
{ {

15
src/Avalonia.Controls/TopLevel.cs

@ -272,7 +272,7 @@ namespace Avalonia.Controls
systemNavigationManager.BackRequested += (_, e) => systemNavigationManager.BackRequested += (_, e) =>
{ {
e.RoutedEvent = BackRequestedEvent; e.RoutedEvent = BackRequestedEvent;
RaiseEvent(e); Dispatcher.UIThread.Send(_ => RaiseEvent(e));
}; };
} }
@ -306,7 +306,7 @@ namespace Avalonia.Controls
if (backRequested) if (backRequested)
{ {
var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent); var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent);
RaiseEvent(backRequestedEventArgs); Dispatcher.UIThread.Send(_ => RaiseEvent(backRequestedEventArgs));
e.Handled = backRequestedEventArgs.Handled; e.Handled = backRequestedEventArgs.Handled;
} }
@ -739,7 +739,7 @@ namespace Avalonia.Controls
private void HandleScalingChanged(double scaling) private void HandleScalingChanged(double scaling)
{ {
LayoutHelper.InvalidateSelfAndChildrenMeasure(this); LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
ScalingChanged?.Invoke(this, EventArgs.Empty); Dispatcher.UIThread.Send(_ => ScalingChanged?.Invoke(this, EventArgs.Empty));
} }
private void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel) private void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel)
@ -786,14 +786,17 @@ namespace Avalonia.Controls
protected virtual void OnOpened(EventArgs e) protected virtual void OnOpened(EventArgs e)
{ {
FrameSize = PlatformImpl?.FrameSize; FrameSize = PlatformImpl?.FrameSize;
Opened?.Invoke(this, e); Dispatcher.UIThread.Send(_ => Opened?.Invoke(this, e));
} }
/// <summary> /// <summary>
/// Raises the <see cref="Closed"/> event. /// Raises the <see cref="Closed"/> event.
/// </summary> /// </summary>
/// <param name="e">The event args.</param> /// <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> /// <summary>
/// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a /// 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; namespace Avalonia.Browser;
internal class BrowserInputPane : IInputPane internal class BrowserInputPane : InputPaneBase
{ {
public BrowserInputPane(JSObject container) public BrowserInputPane(JSObject container)
{ {
InputHelper.SubscribeKeyboardGeometryChange(container, OnGeometryChange); 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) private bool OnGeometryChange(JSObject args)
{ {
var oldState = (OccludedRect, State); var oldState = (OccludedRect, State);
@ -29,7 +25,7 @@ internal class BrowserInputPane : IInputPane
if (oldState != (OccludedRect, State)) if (oldState != (OccludedRect, State))
{ {
StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, null, OccludedRect)); OnStateChanged(new InputPaneStateEventArgs(State, null, OccludedRect));
} }
return true; return true;

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

@ -10,9 +10,9 @@ using static Avalonia.Controls.Platform.IInsetsManager;
namespace Avalonia.Browser namespace Avalonia.Browser
{ {
internal class BrowserInsetsManager : IInsetsManager internal class BrowserInsetsManager : InsetsManagerBase
{ {
public bool? IsSystemBarVisible public override bool? IsSystemBarVisible
{ {
get 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 override Thickness SafeAreaPadding
public Thickness SafeAreaPadding
{ {
get get
{ {
@ -38,11 +36,11 @@ namespace Avalonia.Browser
} }
} }
public Color? SystemBarColor { get; set; } public override Color? SystemBarColor { get; set; }
public void NotifySafeAreaPaddingChanged() 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; namespace Avalonia.Win32.Input;
internal unsafe class WindowsInputPane : IInputPane, IDisposable internal unsafe class WindowsInputPane : InputPaneBase, IDisposable
{ {
private static readonly Lazy<bool> s_inputPaneSupported = new(() => private static readonly Lazy<bool> s_inputPaneSupported = new(() =>
WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.InputPane")); WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.InputPane"));
@ -48,12 +48,6 @@ internal unsafe class WindowsInputPane : IInputPane, IDisposable
return null; 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) private void OnStateChanged(bool showing, UnmanagedMethods.RECT? prcInputPaneScreenLocation)
{ {
@ -65,7 +59,7 @@ internal unsafe class WindowsInputPane : IInputPane, IDisposable
if (oldState != (OccludedRect, State)) 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; namespace Avalonia.iOS;
internal class InsetsManager : IInsetsManager internal class InsetsManager : InsetsManagerBase
{ {
private IAvaloniaViewController? _controller; private IAvaloniaViewController? _controller;
private bool _displayEdgeToEdge = true; private bool _displayEdgeToEdge = true;
@ -16,13 +16,13 @@ internal class InsetsManager : IInsetsManager
{ {
_controller.SafeAreaPaddingChanged += (_, _) => _controller.SafeAreaPaddingChanged += (_, _) =>
{ {
SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding)); OnSafeAreaChanged(new SafeAreaChangedArgs(SafeAreaPadding));
DisplayEdgeToEdgeChanged?.Invoke(this, _displayEdgeToEdge); DisplayEdgeToEdgeChanged?.Invoke(this, _displayEdgeToEdge);
}; };
} }
} }
public bool? IsSystemBarVisible public override bool? IsSystemBarVisible
{ {
get => _controller?.PrefersStatusBarHidden == false; get => _controller?.PrefersStatusBarHidden == false;
set set
@ -33,10 +33,9 @@ internal class InsetsManager : IInsetsManager
} }
} }
} }
public event EventHandler<SafeAreaChangedArgs>? SafeAreaChanged;
public event EventHandler<bool>? DisplayEdgeToEdgeChanged; public event EventHandler<bool>? DisplayEdgeToEdgeChanged;
public bool DisplayEdgeToEdge public override bool DisplayEdgeToEdge
{ {
get => _displayEdgeToEdge; get => _displayEdgeToEdge;
set set
@ -45,12 +44,12 @@ internal class InsetsManager : IInsetsManager
{ {
_displayEdgeToEdge = value; _displayEdgeToEdge = value;
DisplayEdgeToEdgeChanged?.Invoke(this, 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")] [UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")] [SupportedOSPlatform("maccatalyst")]
[SupportedOSPlatform("ios")] [SupportedOSPlatform("ios")]
internal sealed class UIKitInputPane : IInputPane internal sealed class UIKitInputPane : InputPaneBase
{ {
public static UIKitInputPane Instance { get; } = new(); public static UIKitInputPane Instance { get; } = new();
@ -24,11 +24,7 @@ internal sealed class UIKitInputPane : IInputPane
.DefaultCenter .DefaultCenter
.AddObserver(UIKeyboard.WillHideNotification, KeyboardDownNotification); .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 KeyboardDownNotification(NSNotification obj) => RaiseEventFromNotification(false, obj);
private void KeyboardUpNotification(NSNotification obj) => RaiseEventFromNotification(true, obj); private void KeyboardUpNotification(NSNotification obj) => RaiseEventFromNotification(true, obj);
@ -38,7 +34,7 @@ internal sealed class UIKitInputPane : IInputPane
State = isUp ? InputPaneState.Open : InputPaneState.Closed; State = isUp ? InputPaneState.Open : InputPaneState.Closed;
#if MACCATALYST #if MACCATALYST
OccludedRect = default; OccludedRect = default;
StateChanged?.Invoke(this, new InputPaneStateEventArgs( OnStateChanged(new InputPaneStateEventArgs(
State, null, OccludedRect)); State, null, OccludedRect));
#else #else
var startFrame = UIKeyboard.FrameBeginFromNotification(notification); 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); var startRect = new Rect(startFrame.X, startFrame.Y, startFrame.Width, startFrame.Height);
OccludedRect = new Rect(endFrame.X, endFrame.Y, endFrame.Width, endFrame.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)); State, startRect, OccludedRect, TimeSpan.FromSeconds(duration), easing));
#endif #endif
} }

Loading…
Cancel
Save