Browse Source

Fix Toplevel not losing focus with native implementation deactivates. (#20768)

* add failing test

* query the toplevel of the currently focused control instead when losing focus
pull/20773/head
Emmanuel Hansen 4 weeks ago
committed by GitHub
parent
commit
d22683f4eb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 60
      src/Avalonia.Controls/TopLevel.cs
  2. 28
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

60
src/Avalonia.Controls/TopLevel.cs

@ -65,9 +65,9 @@ namespace Avalonia.Controls
/// Defines the <see cref="ActualTransparencyLevel"/> property. /// Defines the <see cref="ActualTransparencyLevel"/> property.
/// </summary> /// </summary>
public static readonly DirectProperty<TopLevel, WindowTransparencyLevel> ActualTransparencyLevelProperty = public static readonly DirectProperty<TopLevel, WindowTransparencyLevel> ActualTransparencyLevelProperty =
AvaloniaProperty.RegisterDirect<TopLevel, WindowTransparencyLevel>(nameof(ActualTransparencyLevel), AvaloniaProperty.RegisterDirect<TopLevel, WindowTransparencyLevel>(nameof(ActualTransparencyLevel),
o => o.ActualTransparencyLevel, o => o.ActualTransparencyLevel,
unsetValue: WindowTransparencyLevel.None); unsetValue: WindowTransparencyLevel.None);
/// <summary> /// <summary>
/// Defines the <see cref="TransparencyBackgroundFallback"/> property. /// Defines the <see cref="TransparencyBackgroundFallback"/> property.
@ -78,7 +78,7 @@ namespace Avalonia.Controls
/// <inheritdoc cref="ThemeVariantScope.ActualThemeVariantProperty" /> /// <inheritdoc cref="ThemeVariantScope.ActualThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty = public static readonly StyledProperty<ThemeVariant> ActualThemeVariantProperty =
ThemeVariantScope.ActualThemeVariantProperty.AddOwner<TopLevel>(); ThemeVariantScope.ActualThemeVariantProperty.AddOwner<TopLevel>();
/// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" /> /// <inheritdoc cref="ThemeVariantScope.RequestedThemeVariantProperty" />
public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty = public static readonly StyledProperty<ThemeVariant?> RequestedThemeVariantProperty =
ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<TopLevel>(); ThemeVariantScope.RequestedThemeVariantProperty.AddOwner<TopLevel>();
@ -102,7 +102,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="BackRequested"/> event. /// Defines the <see cref="BackRequested"/> event.
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> BackRequestedEvent = public static readonly RoutedEvent<RoutedEventArgs> BackRequestedEvent =
RoutedEvent.Register<TopLevel, RoutedEventArgs>(nameof(BackRequested), RoutingStrategies.Bubble); RoutedEvent.Register<TopLevel, RoutedEventArgs>(nameof(BackRequested), RoutingStrategies.Bubble);
private static readonly WeakEvent<IResourceHost, ResourcesChangedEventArgs> private static readonly WeakEvent<IResourceHost, ResourcesChangedEventArgs>
@ -131,7 +131,7 @@ namespace Avalonia.Controls
private readonly PresentationSource _source; private readonly PresentationSource _source;
internal new PresentationSource PresentationSource => _source; internal new PresentationSource PresentationSource => _source;
internal IInputRoot InputRoot => _source; internal IInputRoot InputRoot => _source;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TopLevel"/> class. /// Initializes static members of the <see cref="TopLevel"/> class.
/// </summary> /// </summary>
@ -199,9 +199,9 @@ namespace Avalonia.Controls
_scaling = ValidateScaling(impl.RenderScaling); _scaling = ValidateScaling(impl.RenderScaling);
_actualTransparencyLevel = PlatformImpl.TransparencyLevel; _actualTransparencyLevel = PlatformImpl.TransparencyLevel;
_accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver); _accessKeyHandler = TryGetService<IAccessKeyHandler>(dependencyResolver);
_inputManager = TryGetService<IInputManager>(dependencyResolver); _inputManager = TryGetService<IInputManager>(dependencyResolver);
@ -211,7 +211,7 @@ namespace Avalonia.Controls
_applicationThemeHost = TryGetService<IThemeVariantHost>(dependencyResolver); _applicationThemeHost = TryGetService<IThemeVariantHost>(dependencyResolver);
impl.Closed = HandleClosed; impl.Closed = HandleClosed;
impl.Paint = HandlePaint; impl.Paint = HandlePaint;
@ -257,9 +257,7 @@ namespace Avalonia.Controls
impl.LostFocus += PlatformImpl_LostFocus; impl.LostFocus += PlatformImpl_LostFocus;
if (impl.TryGetFeature<ISystemNavigationManagerImpl>() is { } systemNavigationManager)
if(impl.TryGetFeature<ISystemNavigationManagerImpl>() is {} systemNavigationManager)
{ {
systemNavigationManager.BackRequested += (_, e) => systemNavigationManager.BackRequested += (_, e) =>
{ {
@ -286,14 +284,14 @@ namespace Avalonia.Controls
KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers,
Key = rawKeyEventArgs.Key, Key = rawKeyEventArgs.Key,
PhysicalKey = rawKeyEventArgs.PhysicalKey, PhysicalKey = rawKeyEventArgs.PhysicalKey,
KeyDeviceType= rawKeyEventArgs.KeyDeviceType, KeyDeviceType = rawKeyEventArgs.KeyDeviceType,
KeySymbol = rawKeyEventArgs.KeySymbol KeySymbol = rawKeyEventArgs.KeySymbol
}; };
backRequested = keymap.Any( key => key.Matches(keyEvent)); backRequested = keymap.Any(key => key.Matches(keyEvent));
} }
} }
else if(e is RawPointerEventArgs pointerEventArgs) else if (e is RawPointerEventArgs pointerEventArgs)
{ {
backRequested = pointerEventArgs.Type == RawPointerEventType.XButton1Down; backRequested = pointerEventArgs.Type == RawPointerEventType.XButton1Down;
} }
@ -321,7 +319,7 @@ namespace Avalonia.Controls
/// Gets or sets a method called when the TopLevel's scaling changes. /// Gets or sets a method called when the TopLevel's scaling changes.
/// </summary> /// </summary>
public event EventHandler? ScalingChanged; public event EventHandler? ScalingChanged;
/// <summary> /// <summary>
/// Gets or sets the client size of the window. /// Gets or sets the client size of the window.
/// </summary> /// </summary>
@ -359,7 +357,7 @@ namespace Avalonia.Controls
{ {
get => _actualTransparencyLevel; get => _actualTransparencyLevel;
private set => SetAndRaise(ActualTransparencyLevelProperty, ref _actualTransparencyLevel, value); private set => SetAndRaise(ActualTransparencyLevelProperty, ref _actualTransparencyLevel, value);
} }
/// <summary> /// <summary>
/// Gets or sets the <see cref="IBrush"/> that transparency will blend with when transparency is not supported. /// Gets or sets the <see cref="IBrush"/> that transparency will blend with when transparency is not supported.
@ -439,7 +437,7 @@ namespace Avalonia.Controls
public RendererDiagnostics RendererDiagnostics => Renderer.Diagnostics; public RendererDiagnostics RendererDiagnostics => Renderer.Diagnostics;
internal PixelPoint? LastPointerPosition => _source.GetLastPointerPosition(this); internal PixelPoint? LastPointerPosition => _source.GetLastPointerPosition(this);
/// <summary> /// <summary>
/// Gets the access key handler for the window. /// Gets the access key handler for the window.
/// </summary> /// </summary>
@ -448,7 +446,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the keyboard navigation handler for the window. /// Gets or sets the keyboard navigation handler for the window.
/// </summary> /// </summary>
/// <summary> /// <summary>
/// Helper for setting the color of the platform's system bars. /// Helper for setting the color of the platform's system bars.
/// </summary> /// </summary>
@ -573,7 +571,7 @@ namespace Avalonia.Controls
return Disposable.Create(() => { }); return Disposable.Create(() => { });
} }
} }
/// <summary> /// <summary>
/// Enqueues a callback to be called on the next animation tick /// Enqueues a callback to be called on the next animation tick
/// </summary> /// </summary>
@ -582,7 +580,7 @@ namespace Avalonia.Controls
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
MediaContext.Instance.RequestAnimationFrame(action); MediaContext.Instance.RequestAnimationFrame(action);
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
@ -602,7 +600,7 @@ namespace Avalonia.Controls
private void InvalidateChildInsetsPadding() private void InvalidateChildInsetsPadding()
{ {
if (Content is Control child if (Content is Control child
&& InsetsManager is {} insetsManager) && InsetsManager is { } insetsManager)
{ {
insetsManager.SafeAreaChanged -= InsetsManagerOnSafeAreaChanged; insetsManager.SafeAreaChanged -= InsetsManagerOnSafeAreaChanged;
_insetsPaddings?.Dispose(); _insetsPaddings?.Dispose();
@ -643,12 +641,12 @@ namespace Avalonia.Controls
{ {
_source.Dispose(); _source.Dispose();
StopRendering(); StopRendering();
Debug.Assert(PlatformImpl != null); Debug.Assert(PlatformImpl != null);
// The PlatformImpl is completely invalid at this point // The PlatformImpl is completely invalid at this point
PlatformImpl = null; PlatformImpl = null;
_scaling = 1.0; _scaling = 1.0;
if (_globalStyles is object) if (_globalStyles is object)
{ {
_globalStyles.GlobalStylesAdded -= ((IStyleHost)this).StylesAdded; _globalStyles.GlobalStylesAdded -= ((IStyleHost)this).StylesAdded;
@ -658,14 +656,14 @@ namespace Avalonia.Controls
{ {
_applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged;
} }
_backGestureSubscription?.Dispose(); _backGestureSubscription?.Dispose();
var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
_source.RootVisual = null!; _source.RootVisual = null!;
OnClosed(EventArgs.Empty); OnClosed(EventArgs.Empty);
LayoutManager.Dispose(); LayoutManager.Dispose();
@ -781,8 +779,8 @@ namespace Avalonia.Controls
private static void OnToolTipServiceEnabledChanged(AvaloniaPropertyChangedEventArgs<bool> args) private static void OnToolTipServiceEnabledChanged(AvaloniaPropertyChangedEventArgs<bool> args)
{ {
if (args.GetNewValue<bool>() if (args.GetNewValue<bool>()
&& args.Priority != BindingPriority.Inherited && args.Priority != BindingPriority.Inherited
&& args.Sender is Visual visual && args.Sender is Visual visual
&& GetTopLevel(visual) is { } topLevel) && GetTopLevel(visual) is { } topLevel)
{ {
topLevel.UpdateToolTip(visual.Bounds.Translate((Vector)visual.TranslatePoint(default, topLevel)!)); topLevel.UpdateToolTip(visual.Bounds.Translate((Vector)visual.TranslatePoint(default, topLevel)!));
@ -803,11 +801,7 @@ namespace Avalonia.Controls
void PlatformImpl_LostFocus() void PlatformImpl_LostFocus()
{ {
var focused = (Visual?)FocusManager?.GetFocusedElement(); var focused = TopLevel.GetTopLevel((Visual?)FocusManager?.GetFocusedElement());
if (focused == null)
return;
while (focused.VisualParent != null)
focused = focused.VisualParent;
if (focused == this) if (focused == this)
KeyboardDevice.Instance?.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None, false); KeyboardDevice.Instance?.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None, false);

28
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -253,6 +253,34 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void TopLevel_Should_Unfocus_When_Impl_Focus_Is_Lost()
{
using (UnitTestApplication.Start(TestServices.RealFocus))
{
var impl = CreateMockTopLevelImpl(true);
var content = new TextBox()
{
Focusable = true
};
var target = new TestTopLevel(impl.Object)
{
Template = CreateTemplate(),
Focusable = true,
Content = content
};
target.LayoutManager.ExecuteInitialLayoutPass();
content.Focus();
Assert.True(content.IsFocused);
impl.Object.LostFocus?.Invoke();
Assert.False(content.IsFocused);
}
}
[Fact] [Fact]
public void Reacts_To_Changes_In_Global_Styles() public void Reacts_To_Changes_In_Global_Styles()
{ {

Loading…
Cancel
Save