Browse Source

Merge remote-tracking branch 'origin/master' into android-profile

pull/11429/head
Max Katz 3 years ago
parent
commit
65095a9b6b
  1. 2
      packages/Avalonia/Avalonia.csproj
  2. 18
      src/Avalonia.Base/Diagnostics/AppliedStyle.cs
  3. 4
      src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs
  4. 4
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  5. 56
      src/Avalonia.Base/Input/FocusManager.cs
  6. 36
      src/Avalonia.Base/Input/IFocusManager.cs
  7. 3
      src/Avalonia.Base/Input/IFocusScope.cs
  8. 4
      src/Avalonia.Base/Input/IInputElement.cs
  9. 8
      src/Avalonia.Base/Input/IInputRoot.cs
  10. 8
      src/Avalonia.Base/Input/IKeyboardDevice.cs
  11. 17
      src/Avalonia.Base/Input/InputElement.cs
  12. 4
      src/Avalonia.Base/Input/KeyboardDevice.cs
  13. 4
      src/Avalonia.Base/Input/KeyboardNavigationHandler.cs
  14. 2
      src/Avalonia.Base/Input/MouseDevice.cs
  15. 6
      src/Avalonia.Base/Input/Navigation/TabNavigation.cs
  16. 2
      src/Avalonia.Base/Input/PenDevice.cs
  17. 2
      src/Avalonia.Base/Input/TouchDevice.cs
  18. 4
      src/Avalonia.Base/StyledElement.cs
  19. 3
      src/Avalonia.Base/Styling/Activators/IStyleActivator.cs
  20. 3
      src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs
  21. 12
      src/Avalonia.Base/Styling/ChildSelector.cs
  22. 12
      src/Avalonia.Base/Styling/DescendentSelector.cs
  23. 24
      src/Avalonia.Base/Styling/ISetter.cs
  24. 2
      src/Avalonia.Base/Styling/ISetterInstance.cs
  25. 4
      src/Avalonia.Base/Styling/ISetterValue.cs
  26. 3
      src/Avalonia.Base/Styling/IStyleInstance.cs
  27. 12
      src/Avalonia.Base/Styling/NestingSelector.cs
  28. 12
      src/Avalonia.Base/Styling/NotSelector.cs
  29. 14
      src/Avalonia.Base/Styling/NthChildSelector.cs
  30. 2
      src/Avalonia.Base/Styling/NthLastChildSelector.cs
  31. 12
      src/Avalonia.Base/Styling/OrSelector.cs
  32. 12
      src/Avalonia.Base/Styling/PropertyEqualsSelector.cs
  33. 14
      src/Avalonia.Base/Styling/Selector.cs
  34. 4
      src/Avalonia.Base/Styling/SelectorMatch.cs
  35. 4
      src/Avalonia.Base/Styling/Setter.cs
  36. 12
      src/Avalonia.Base/Styling/SetterBase.cs
  37. 6
      src/Avalonia.Base/Styling/StyleBase.cs
  38. 12
      src/Avalonia.Base/Styling/TemplateSelector.cs
  39. 12
      src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs
  40. 5
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  41. 2
      src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs
  42. 2
      src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs
  43. 2
      src/Avalonia.Controls/Calendar/Calendar.cs
  44. 3
      src/Avalonia.Controls/ComboBox.cs
  45. 6
      src/Avalonia.Controls/ContextMenu.cs
  46. 2
      src/Avalonia.Controls/Control.cs
  47. 11
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  48. 6
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  49. 4
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  50. 11
      src/Avalonia.Controls/ItemsControl.cs
  51. 6
      src/Avalonia.Controls/Primitives/Popup.cs
  52. 5
      src/Avalonia.Controls/TopLevel.cs
  53. 3
      src/Avalonia.Controls/TreeView.cs
  54. 2
      src/Avalonia.Controls/TreeViewItem.cs
  55. 14
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  56. 4
      src/Avalonia.Controls/WindowBase.cs
  57. 8
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  58. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  59. 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs
  60. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  61. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs
  62. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  63. 6
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  64. 2
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  65. 33
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  66. 4
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  67. 1
      src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs
  68. 2
      src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs
  69. 4
      src/Skia/Avalonia.Skia/PictureRenderTarget.cs
  70. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  71. 2
      src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs
  72. 2
      src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs
  73. 58
      tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs
  74. 4
      tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs
  75. 36
      tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs
  76. 10
      tests/Avalonia.Controls.UnitTests/FlyoutTests.cs
  77. 6
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  78. 8
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  79. 4
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  80. 11
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  81. 8
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  82. 16
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  83. 92
      tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs
  84. 4
      tests/Avalonia.LeakTests/ControlTests.cs
  85. 4
      tests/Avalonia.UnitTests/StyleHelpers.cs
  86. 1
      tests/Avalonia.UnitTests/TestRoot.cs

2
packages/Avalonia/Avalonia.csproj

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="0.0.15" /> <PackageReference Include="Avalonia.BuildServices" Version="0.0.16" />
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" /> <ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj"> <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

18
src/Avalonia.Base/Diagnostics/AppliedStyle.cs

@ -0,0 +1,18 @@
using Avalonia.Styling;
namespace Avalonia.Diagnostics
{
public class AppliedStyle
{
private readonly IStyleInstance _instance;
internal AppliedStyle(IStyleInstance instance)
{
_instance = instance;
}
public bool HasActivator => _instance.HasActivator;
public bool IsActive => _instance.IsActive;
public StyleBase Style => (StyleBase)_instance.Source;
}
}

4
src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs

@ -11,9 +11,9 @@ namespace Avalonia.Diagnostics
/// <summary> /// <summary>
/// Currently applied styles. /// Currently applied styles.
/// </summary> /// </summary>
public IReadOnlyList<IStyleInstance> AppliedStyles { get; } public IReadOnlyList<AppliedStyle> AppliedStyles { get; }
public StyleDiagnostics(IReadOnlyList<IStyleInstance> appliedStyles) public StyleDiagnostics(IReadOnlyList<AppliedStyle> appliedStyles)
{ {
AppliedStyles = appliedStyles; AppliedStyles = appliedStyles;
} }

4
src/Avalonia.Base/Input/AccessKeyHandler.cs

@ -141,9 +141,11 @@ namespace Avalonia.Input
if (MainMenu == null || !MainMenu.IsOpen) if (MainMenu == null || !MainMenu.IsOpen)
{ {
var focusManager = FocusManager.GetFocusManager(e.Source as IInputElement);
// TODO: Use FocusScopes to store the current element and restore it when context menu is closed. // TODO: Use FocusScopes to store the current element and restore it when context menu is closed.
// Save currently focused input element. // Save currently focused input element.
_restoreFocusElement = FocusManager.Instance?.Current; _restoreFocusElement = focusManager?.GetFocusedElement();
// When Alt is pressed without a main menu, or with a closed main menu, show // When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File"). // access key markers in the window (i.e. "_File").

56
src/Avalonia.Base/Input/FocusManager.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
@ -10,6 +11,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Manages focus for the application. /// Manages focus for the application.
/// </summary> /// </summary>
[PrivateApi]
public class FocusManager : IFocusManager public class FocusManager : IFocusManager
{ {
/// <summary> /// <summary>
@ -29,15 +31,12 @@ namespace Avalonia.Input
RoutingStrategies.Tunnel); RoutingStrategies.Tunnel);
} }
/// <summary> private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
/// Gets the instance of the <see cref="IFocusManager"/>.
/// </summary>
public static IFocusManager? Instance => AvaloniaLocator.Current.GetService<IFocusManager>();
/// <summary> /// <summary>
/// Gets the currently focused <see cref="IInputElement"/>. /// Gets the currently focused <see cref="IInputElement"/>.
/// </summary> /// </summary>
public IInputElement? Current => KeyboardDevice.Instance?.FocusedElement; public IInputElement? GetFocusedElement() => Current;
/// <summary> /// <summary>
/// Gets the current focus scope. /// Gets the current focus scope.
@ -54,7 +53,7 @@ namespace Avalonia.Input
/// <param name="control">The control to focus.</param> /// <param name="control">The control to focus.</param>
/// <param name="method">The method by which focus was changed.</param> /// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param> /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Focus( public bool Focus(
IInputElement? control, IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
@ -67,7 +66,7 @@ namespace Avalonia.Input
if (scope != null) if (scope != null)
{ {
Scope = scope; Scope = scope;
SetFocusedElement(scope, control, method, keyModifiers); return SetFocusedElement(scope, control, method, keyModifiers);
} }
} }
else if (Current != null) else if (Current != null)
@ -79,28 +78,29 @@ namespace Avalonia.Input
_focusScopes.TryGetValue(scope, out var element) && _focusScopes.TryGetValue(scope, out var element) &&
element != null) element != null)
{ {
Focus(element, method); return Focus(element, method);
return;
} }
} }
if (Scope is object) if (Scope is object)
{ {
// Couldn't find a focus scope, clear focus. // Couldn't find a focus scope, clear focus.
SetFocusedElement(Scope, null); return SetFocusedElement(Scope, null);
} }
} }
return false;
} }
public IInputElement? GetFocusedElement(IInputElement e) public void ClearFocus()
{ {
if (e is IFocusScope scope) Focus(null);
{ }
_focusScopes.TryGetValue(scope, out var result);
return result;
}
return null; public IInputElement? GetFocusedElement(IFocusScope scope)
{
_focusScopes.TryGetValue(scope, out var result);
return result;
} }
/// <summary> /// <summary>
@ -114,7 +114,7 @@ namespace Avalonia.Input
/// If the specified scope is the current <see cref="Scope"/> then the keyboard focus /// If the specified scope is the current <see cref="Scope"/> then the keyboard focus
/// will change. /// will change.
/// </remarks> /// </remarks>
public void SetFocusedElement( public bool SetFocusedElement(
IFocusScope scope, IFocusScope scope,
IInputElement? element, IInputElement? element,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
@ -124,7 +124,7 @@ namespace Avalonia.Input
if (element is not null && !CanFocus(element)) if (element is not null && !CanFocus(element))
{ {
return; return false;
} }
if (_focusScopes.TryGetValue(scope, out var existingElement)) if (_focusScopes.TryGetValue(scope, out var existingElement))
@ -144,6 +144,8 @@ namespace Avalonia.Input
{ {
KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers); KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers);
} }
return true;
} }
/// <summary> /// <summary>
@ -185,6 +187,20 @@ namespace Avalonia.Input
public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope; public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope;
/// <summary>
/// Public API customers should use TopLevel.GetTopLevel(control).FocusManager.
/// But since we have split projects, we can't access TopLevel from Avalonia.Base.
/// That's why we need this helper method instead.
/// </summary>
internal static FocusManager? GetFocusManager(IInputElement? element)
{
// Element might not be a visual, and not attached to the root.
// But IFocusManager is always expected to be a FocusManager.
return (FocusManager?)((element as Visual)?.VisualRoot as IInputRoot)?.FocusManager
// In our unit tests some elements might not have a root. Remove when we migrate to headless tests.
?? (FocusManager?)AvaloniaLocator.Current.GetService<IFocusManager>();
}
/// <summary> /// <summary>
/// Checks if the specified element can be focused. /// Checks if the specified element can be focused.
/// </summary> /// </summary>
@ -237,7 +253,7 @@ namespace Avalonia.Input
{ {
if (element is IInputElement inputElement && CanFocus(inputElement)) if (element is IInputElement inputElement && CanFocus(inputElement))
{ {
Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.KeyModifiers); inputElement.Focus(NavigationMethod.Pointer, ev.KeyModifiers);
break; break;
} }

36
src/Avalonia.Base/Input/IFocusManager.cs

@ -11,40 +11,12 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets the currently focused <see cref="IInputElement"/>. /// Gets the currently focused <see cref="IInputElement"/>.
/// </summary> /// </summary>
IInputElement? Current { get; } IInputElement? GetFocusedElement();
/// <summary> /// <summary>
/// Gets the current focus scope. /// Clears currently focused element.
/// </summary> /// </summary>
IFocusScope? Scope { get; } [Unstable("This API might be removed in 11.x minor updates. Please consider focusing another element instead of removing focus at all for better UX.")]
void ClearFocus();
/// <summary>
/// Focuses a control.
/// </summary>
/// <param name="control">The control to focus.</param>
/// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
void Focus(
IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None);
/// <summary>
/// Notifies the focus manager of a change in focus scope.
/// </summary>
/// <param name="scope">The new focus scope.</param>
/// <remarks>
/// This should not be called by client code. It is called by an <see cref="IFocusScope"/>
/// when it activates, e.g. when a Window is activated.
/// </remarks>
void SetFocusScope(IFocusScope scope);
/// <summary>
/// Notifies the focus manager that a focus scope has been removed.
/// </summary>
/// <param name="scope">The focus scope to be removed.</param>
/// This should not be called by client code. It is called by an <see cref="IFocusScope"/>
/// when it deactivates or closes, e.g. when a Window is closed.
void RemoveFocusScope(IFocusScope scope);
} }
} }

3
src/Avalonia.Base/Input/IFocusScope.cs

@ -1,5 +1,8 @@
using Avalonia.Metadata;
namespace Avalonia.Input namespace Avalonia.Input
{ {
[NotClientImplementable]
public interface IFocusScope public interface IFocusScope
{ {
} }

4
src/Avalonia.Base/Input/IInputElement.cs

@ -119,7 +119,9 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Focuses the control. /// Focuses the control.
/// </summary> /// </summary>
void Focus(); /// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None);
/// <summary> /// <summary>
/// Gets the key bindings for the element. /// Gets the key bindings for the element.

8
src/Avalonia.Base/Input/IInputRoot.cs

@ -18,6 +18,14 @@ namespace Avalonia.Input
/// </summary> /// </summary>
IKeyboardNavigationHandler KeyboardNavigationHandler { get; } IKeyboardNavigationHandler KeyboardNavigationHandler { get; }
/// <summary>
/// Gets focus manager of the root.
/// </summary>
/// <remarks>
/// Focus manager can be null only if application wasn't initialized yet.
/// </remarks>
IFocusManager? FocusManager { get; }
/// <summary> /// <summary>
/// Gets or sets the input element that the pointer is currently over. /// Gets or sets the input element that the pointer is currently over.
/// </summary> /// </summary>

8
src/Avalonia.Base/Input/IKeyboardDevice.cs

@ -44,13 +44,7 @@ namespace Avalonia.Input
} }
[NotClientImplementable] [NotClientImplementable]
public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged public interface IKeyboardDevice : IInputDevice
{ {
IInputElement? FocusedElement { get; }
void SetFocusedElement(
IInputElement? element,
NavigationMethod method,
KeyModifiers modifiers);
} }
} }

17
src/Avalonia.Base/Input/InputElement.cs

@ -458,9 +458,10 @@ namespace Avalonia.Input
SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value); SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, value);
PseudoClasses.Set(":disabled", !value); PseudoClasses.Set(":disabled", !value);
if (!IsEffectivelyEnabled && FocusManager.Instance?.Current == this) if (!IsEffectivelyEnabled && FocusManager.GetFocusManager(this) is {} focusManager
&& Equals(focusManager.GetFocusedElement(), this))
{ {
FocusManager.Instance?.Focus(null); focusManager.ClearFocus();
} }
} }
} }
@ -491,12 +492,10 @@ namespace Avalonia.Input
public GestureRecognizerCollection GestureRecognizers public GestureRecognizerCollection GestureRecognizers
=> _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this)); => _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this));
/// <summary> /// <inheritdoc />
/// Focuses the control. public bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None)
/// </summary>
public void Focus()
{ {
FocusManager.Instance?.Focus(this); return FocusManager.GetFocusManager(this)?.Focus(this, method, keyModifiers) ?? false;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -506,7 +505,7 @@ namespace Avalonia.Input
if (IsFocused) if (IsFocused)
{ {
FocusManager.Instance?.Focus(null); FocusManager.GetFocusManager(this)?.ClearFocus();
} }
} }
@ -649,7 +648,7 @@ namespace Avalonia.Input
} }
else if (change.Property == IsVisibleProperty && !change.GetNewValue<bool>() && IsFocused) else if (change.Property == IsVisibleProperty && !change.GetNewValue<bool>() && IsFocused)
{ {
FocusManager.Instance?.Focus(null); FocusManager.GetFocusManager(this)?.ClearFocus();
} }
} }

4
src/Avalonia.Base/Input/KeyboardDevice.cs

@ -3,9 +3,11 @@ using System.Runtime.CompilerServices;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Metadata;
namespace Avalonia.Input namespace Avalonia.Input
{ {
[PrivateApi]
public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged
{ {
private IInputElement? _focusedElement; private IInputElement? _focusedElement;
@ -13,7 +15,7 @@ namespace Avalonia.Input
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
public static IKeyboardDevice? Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>(); internal static KeyboardDevice? Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>() as KeyboardDevice;
public IInputManager? InputManager => AvaloniaLocator.Current.GetService<IInputManager>(); public IInputManager? InputManager => AvaloniaLocator.Current.GetService<IInputManager>();

4
src/Avalonia.Base/Input/KeyboardNavigationHandler.cs

@ -88,7 +88,7 @@ namespace Avalonia.Input
var method = direction == NavigationDirection.Next || var method = direction == NavigationDirection.Next ||
direction == NavigationDirection.Previous ? direction == NavigationDirection.Previous ?
NavigationMethod.Tab : NavigationMethod.Directional; NavigationMethod.Tab : NavigationMethod.Directional;
FocusManager.Instance?.Focus(next, method, keyModifiers); next.Focus(method, keyModifiers);
} }
} }
@ -99,7 +99,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnKeyDown(object? sender, KeyEventArgs e) protected virtual void OnKeyDown(object? sender, KeyEventArgs e)
{ {
var current = FocusManager.Instance?.Current; var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement();
if (current != null && e.Key == Key.Tab) if (current != null && e.Key == Key.Tab)
{ {

2
src/Avalonia.Base/Input/MouseDevice.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities; using Avalonia.Utilities;
#pragma warning disable CS0618 #pragma warning disable CS0618
@ -11,6 +12,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Represents a mouse device. /// Represents a mouse device.
/// </summary> /// </summary>
[PrivateApi]
public class MouseDevice : IMouseDevice, IDisposable public class MouseDevice : IMouseDevice, IDisposable
{ {
private int _clickCount; private int _clickCount;

6
src/Avalonia.Base/Input/Navigation/TabNavigation.cs

@ -190,9 +190,11 @@ namespace Avalonia.Input.Navigation
private static IInputElement? FocusedElement(IInputElement? e) private static IInputElement? FocusedElement(IInputElement? e)
{ {
// Focus delegation is enabled only if keyboard focus is outside the container // Focus delegation is enabled only if keyboard focus is outside the container
if (e != null && !e.IsKeyboardFocusWithin) if (e != null && !e.IsKeyboardFocusWithin && e is IFocusScope scope)
{ {
var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e); var focusManager = FocusManager.GetFocusManager(e);
var focusedElement = focusManager?.GetFocusedElement(scope);
if (focusedElement != null) if (focusedElement != null)
{ {
if (!IsFocusScope(e)) if (!IsFocusScope(e))

2
src/Avalonia.Base/Input/PenDevice.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
#pragma warning disable CS0618 #pragma warning disable CS0618
@ -11,6 +12,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Represents a pen/stylus device. /// Represents a pen/stylus device.
/// </summary> /// </summary>
[PrivateApi]
public class PenDevice : IPenDevice, IDisposable public class PenDevice : IPenDevice, IDisposable
{ {
private readonly Dictionary<long, Pointer> _pointers = new(); private readonly Dictionary<long, Pointer> _pointers = new();

2
src/Avalonia.Base/Input/TouchDevice.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
#pragma warning disable CS0618 #pragma warning disable CS0618
@ -14,6 +15,7 @@ namespace Avalonia.Input
/// <remarks> /// <remarks>
/// This class is supposed to be used on per-toplevel basis, don't use a shared one /// This class is supposed to be used on per-toplevel basis, don't use a shared one
/// </remarks> /// </remarks>
[PrivateApi]
public class TouchDevice : IPointerDevice, IDisposable public class TouchDevice : IPointerDevice, IDisposable
{ {
private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>(); private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();

4
src/Avalonia.Base/StyledElement.cs

@ -420,12 +420,12 @@ namespace Avalonia
internal StyleDiagnostics GetStyleDiagnosticsInternal() internal StyleDiagnostics GetStyleDiagnosticsInternal()
{ {
var styles = new List<IStyleInstance>(); var styles = new List<AppliedStyle>();
foreach (var frame in GetValueStore().Frames) foreach (var frame in GetValueStore().Frames)
{ {
if (frame is IStyleInstance style) if (frame is IStyleInstance style)
styles.Add(style); styles.Add(new(style));
} }
return new StyleDiagnostics(styles); return new StyleDiagnostics(styles);

3
src/Avalonia.Base/Styling/Activators/IStyleActivator.cs

@ -15,8 +15,7 @@ namespace Avalonia.Styling.Activators
/// - The activation state can be re-evaluated at any time by calling <see cref="GetIsActive"/> /// - The activation state can be re-evaluated at any time by calling <see cref="GetIsActive"/>
/// - No error or completion messages /// - No error or completion messages
/// </remarks> /// </remarks>
[Unstable] internal interface IStyleActivator : IDisposable
public interface IStyleActivator : IDisposable
{ {
/// <summary> /// <summary>
/// Gets a value indicating whether the style is subscribed. /// Gets a value indicating whether the style is subscribed.

3
src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs

@ -5,8 +5,7 @@ namespace Avalonia.Styling.Activators
/// <summary> /// <summary>
/// Receives notifications from an <see cref="IStyleActivator"/>. /// Receives notifications from an <see cref="IStyleActivator"/>.
/// </summary> /// </summary>
[Unstable] internal interface IStyleActivatorSink
public interface IStyleActivatorSink
{ {
/// <summary> /// <summary>
/// Called when the subscribed activator value changes. /// Called when the subscribed activator value changes.

12
src/Avalonia.Base/Styling/ChildSelector.cs

@ -19,13 +19,13 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate; internal override bool InTemplate => _parent.InTemplate;
/// <inheritdoc/> /// <inheritdoc/>
public override bool IsCombinator => true; internal override bool IsCombinator => true;
/// <inheritdoc/> /// <inheritdoc/>
public override Type? TargetType => null; internal override Type? TargetType => null;
public override string ToString(Style? owner) public override string ToString(Style? owner)
{ {
@ -37,7 +37,7 @@ namespace Avalonia.Styling
return _selectorString; return _selectorString;
} }
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
var controlParent = ((ILogical)control).LogicalParent; var controlParent = ((ILogical)control).LogicalParent;
@ -64,7 +64,7 @@ namespace Avalonia.Styling
} }
} }
protected override Selector? MovePrevious() => null; private protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => _parent; private protected override Selector? MovePreviousOrParent() => _parent;
} }
} }

12
src/Avalonia.Base/Styling/DescendentSelector.cs

@ -17,13 +17,13 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool IsCombinator => true; internal override bool IsCombinator => true;
/// <inheritdoc/> /// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate; internal override bool InTemplate => _parent.InTemplate;
/// <inheritdoc/> /// <inheritdoc/>
public override Type? TargetType => null; internal override Type? TargetType => null;
public override string ToString(Style? owner) public override string ToString(Style? owner)
{ {
@ -35,7 +35,7 @@ namespace Avalonia.Styling
return _selectorString; return _selectorString;
} }
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
var c = (ILogical)control; var c = (ILogical)control;
var descendantMatches = new OrActivatorBuilder(); var descendantMatches = new OrActivatorBuilder();
@ -69,7 +69,7 @@ namespace Avalonia.Styling
} }
} }
protected override Selector? MovePrevious() => null; private protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => _parent; private protected override Selector? MovePreviousOrParent() => _parent;
} }
} }

24
src/Avalonia.Base/Styling/ISetter.cs

@ -1,24 +0,0 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Styling
{
/// <summary>
/// Represents a setter for a <see cref="Style"/>.
/// </summary>
[NotClientImplementable]
public interface ISetter
{
/// <summary>
/// Instances a setter on a control.
/// </summary>
/// <param name="styleInstance">The style which contains the setter.</param>
/// <param name="target">The control.</param>
/// <returns>An <see cref="ISetterInstance"/>.</returns>
/// <remarks>
/// This method should return an <see cref="ISetterInstance"/> which can be used to apply
/// the setter to the specified control.
/// </remarks>
ISetterInstance Instance(IStyleInstance styleInstance, StyledElement target);
}
}

2
src/Avalonia.Base/Styling/ISetterInstance.cs

@ -3,7 +3,7 @@
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
/// <summary> /// <summary>
/// Represents an <see cref="ISetter"/> that has been instanced on a control. /// Represents a <see cref="Setter"/> that has been instanced on a control.
/// </summary> /// </summary>
[Unstable] [Unstable]
public interface ISetterInstance public interface ISetterInstance

4
src/Avalonia.Base/Styling/ISetterValue.cs

@ -3,13 +3,13 @@
namespace Avalonia.Styling namespace Avalonia.Styling
{ {
/// <summary> /// <summary>
/// Customizes the behavior of a class when added as a value to an <see cref="ISetter"/>. /// Customizes the behavior of a class when added as a value to a <see cref="SetterBase"/>.
/// </summary> /// </summary>
public interface ISetterValue public interface ISetterValue
{ {
/// <summary> /// <summary>
/// Notifies that the object has been added as a setter value. /// Notifies that the object has been added as a setter value.
/// </summary> /// </summary>
void Initialize(ISetter setter); void Initialize(SetterBase setter);
} }
} }

3
src/Avalonia.Base/Styling/IStyleInstance.cs

@ -5,8 +5,7 @@ namespace Avalonia.Styling
/// <summary> /// <summary>
/// Represents a <see cref="Style"/> that has been instanced on a control. /// Represents a <see cref="Style"/> that has been instanced on a control.
/// </summary> /// </summary>
[Unstable] internal interface IStyleInstance
public interface IStyleInstance
{ {
/// <summary> /// <summary>
/// Gets the source style. /// Gets the source style.

12
src/Avalonia.Base/Styling/NestingSelector.cs

@ -7,13 +7,13 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
internal class NestingSelector : Selector internal class NestingSelector : Selector
{ {
public override bool InTemplate => false; internal override bool InTemplate => false;
public override bool IsCombinator => false; internal override bool IsCombinator => false;
public override Type? TargetType => null; internal override Type? TargetType => null;
public override string ToString(Style? owner) => owner?.Parent?.ToString() ?? "^"; public override string ToString(Style? owner) => owner?.Parent?.ToString() ?? "^";
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
if (parent is Style s && s.Selector is not null) if (parent is Style s && s.Selector is not null)
{ {
@ -32,7 +32,7 @@ namespace Avalonia.Styling
"Nesting selector was specified but cannot determine parent selector."); "Nesting selector was specified but cannot determine parent selector.");
} }
protected override Selector? MovePrevious() => null; private protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => null; private protected override Selector? MovePreviousOrParent() => null;
} }
} }

12
src/Avalonia.Base/Styling/NotSelector.cs

@ -26,13 +26,13 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool InTemplate => _argument.InTemplate; internal override bool InTemplate => _argument.InTemplate;
/// <inheritdoc/> /// <inheritdoc/>
public override bool IsCombinator => false; internal override bool IsCombinator => false;
/// <inheritdoc/> /// <inheritdoc/>
public override Type? TargetType => _previous?.TargetType; internal override Type? TargetType => _previous?.TargetType;
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString(Style? owner) public override string ToString(Style? owner)
@ -45,7 +45,7 @@ namespace Avalonia.Styling
return _selectorString; return _selectorString;
} }
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
var innerResult = _argument.Match(control, parent, subscribe); var innerResult = _argument.Match(control, parent, subscribe);
@ -66,7 +66,7 @@ namespace Avalonia.Styling
} }
} }
protected override Selector? MovePrevious() => _previous; private protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous; private protected override Selector? MovePreviousOrParent() => _previous;
} }
} }

14
src/Avalonia.Base/Styling/NthChildSelector.cs

@ -12,7 +12,7 @@ namespace Avalonia.Styling
/// <remarks> /// <remarks>
/// Element indices are 1-based. /// Element indices are 1-based.
/// </remarks> /// </remarks>
public class NthChildSelector : Selector internal class NthChildSelector : Selector
{ {
private const string NthChildSelectorName = "nth-child"; private const string NthChildSelectorName = "nth-child";
private const string NthLastChildSelectorName = "nth-last-child"; private const string NthLastChildSelectorName = "nth-last-child";
@ -39,16 +39,16 @@ namespace Avalonia.Styling
} }
public override bool InTemplate => _previous?.InTemplate ?? false; internal override bool InTemplate => _previous?.InTemplate ?? false;
public override bool IsCombinator => false; internal override bool IsCombinator => false;
public override Type? TargetType => _previous?.TargetType; internal override Type? TargetType => _previous?.TargetType;
public int Step { get; } public int Step { get; }
public int Offset { get; } public int Offset { get; }
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
if (!(control is ILogical logical)) if (!(control is ILogical logical))
{ {
@ -103,8 +103,8 @@ namespace Avalonia.Styling
return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
} }
protected override Selector? MovePrevious() => _previous; private protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous; private protected override Selector? MovePreviousOrParent() => _previous;
public override string ToString(Style? owner) public override string ToString(Style? owner)
{ {

2
src/Avalonia.Base/Styling/NthLastChildSelector.cs

@ -8,7 +8,7 @@ namespace Avalonia.Styling
/// <remarks> /// <remarks>
/// Element indices are 1-based. /// Element indices are 1-based.
/// </remarks> /// </remarks>
public class NthLastChildSelector : NthChildSelector internal class NthLastChildSelector : NthChildSelector
{ {
/// <summary> /// <summary>
/// Creates an instance of <see cref="NthLastChildSelector"/> /// Creates an instance of <see cref="NthLastChildSelector"/>

12
src/Avalonia.Base/Styling/OrSelector.cs

@ -36,13 +36,13 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool InTemplate => false; internal override bool InTemplate => false;
/// <inheritdoc/> /// <inheritdoc/>
public override bool IsCombinator => false; internal override bool IsCombinator => false;
/// <inheritdoc/> /// <inheritdoc/>
public override Type? TargetType => _targetType ??= EvaluateTargetType(); internal override Type? TargetType => _targetType ??= EvaluateTargetType();
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString(Style? owner) public override string ToString(Style? owner)
@ -55,7 +55,7 @@ namespace Avalonia.Styling
return _selectorString; return _selectorString;
} }
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
var activators = new OrActivatorBuilder(); var activators = new OrActivatorBuilder();
var neverThisInstance = false; var neverThisInstance = false;
@ -94,8 +94,8 @@ namespace Avalonia.Styling
} }
} }
protected override Selector? MovePrevious() => null; private protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => null; private protected override Selector? MovePreviousOrParent() => null;
internal override void ValidateNestingSelector(bool inControlTheme) internal override void ValidateNestingSelector(bool inControlTheme)
{ {

12
src/Avalonia.Base/Styling/PropertyEqualsSelector.cs

@ -28,13 +28,13 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool InTemplate => _previous?.InTemplate ?? false; internal override bool InTemplate => _previous?.InTemplate ?? false;
/// <inheritdoc/> /// <inheritdoc/>
public override bool IsCombinator => false; internal override bool IsCombinator => false;
/// <inheritdoc/> /// <inheritdoc/>
public override Type? TargetType => _previous?.TargetType; internal override Type? TargetType => _previous?.TargetType;
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString(Style? owner) public override string ToString(Style? owner)
@ -73,7 +73,7 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
if (subscribe) if (subscribe)
{ {
@ -88,8 +88,8 @@ namespace Avalonia.Styling
} }
protected override Selector? MovePrevious() => _previous; private protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous; private protected override Selector? MovePreviousOrParent() => _previous;
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)]
[UnconditionalSuppressMessage("Trimming", "IL2067", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)] [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)]

14
src/Avalonia.Base/Styling/Selector.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// Gets a value indicating whether either this selector or a previous selector has moved /// Gets a value indicating whether either this selector or a previous selector has moved
/// into a template. /// into a template.
/// </summary> /// </summary>
public abstract bool InTemplate { get; } internal abstract bool InTemplate { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether this selector is a combinator. /// Gets a value indicating whether this selector is a combinator.
@ -22,12 +22,12 @@ namespace Avalonia.Styling
/// <remarks> /// <remarks>
/// A combinator is a selector such as Child or Descendent which links simple selectors. /// A combinator is a selector such as Child or Descendent which links simple selectors.
/// </remarks> /// </remarks>
public abstract bool IsCombinator { get; } internal abstract bool IsCombinator { get; }
/// <summary> /// <summary>
/// Gets the target type of the selector, if available. /// Gets the target type of the selector, if available.
/// </summary> /// </summary>
public abstract Type? TargetType { get; } internal abstract Type? TargetType { get; }
/// <summary> /// <summary>
/// Tries to match the selector with a control. /// Tries to match the selector with a control.
@ -41,7 +41,7 @@ namespace Avalonia.Styling
/// or simply return an immediate result. /// or simply return an immediate result.
/// </param> /// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns> /// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true) internal SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true)
{ {
// First match the selector until a combinator is found. Selectors are stored from // First match the selector until a combinator is found. Selectors are stored from
// right-to-left, so MatchUntilCombinator reverses this order because the type selector // right-to-left, so MatchUntilCombinator reverses this order because the type selector
@ -88,17 +88,17 @@ namespace Avalonia.Styling
/// or simply return an immediate result. /// or simply return an immediate result.
/// </param> /// </param>
/// <returns>A <see cref="SelectorMatch"/>.</returns> /// <returns>A <see cref="SelectorMatch"/>.</returns>
protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe); private protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe);
/// <summary> /// <summary>
/// Moves to the previous selector. /// Moves to the previous selector.
/// </summary> /// </summary>
protected abstract Selector? MovePrevious(); private protected abstract Selector? MovePrevious();
/// <summary> /// <summary>
/// Moves to the previous selector or the parent selector. /// Moves to the previous selector or the parent selector.
/// </summary> /// </summary>
protected abstract Selector? MovePreviousOrParent(); private protected abstract Selector? MovePreviousOrParent();
internal virtual void ValidateNestingSelector(bool inControlTheme) internal virtual void ValidateNestingSelector(bool inControlTheme)
{ {

4
src/Avalonia.Base/Styling/SelectorMatch.cs

@ -8,7 +8,7 @@ namespace Avalonia.Styling
/// <summary> /// <summary>
/// Describes how a <see cref="SelectorMatch"/> matches a control and its type. /// Describes how a <see cref="SelectorMatch"/> matches a control and its type.
/// </summary> /// </summary>
public enum SelectorMatchResult internal enum SelectorMatchResult
{ {
/// <summary> /// <summary>
/// The selector never matches this type. /// The selector never matches this type.
@ -43,7 +43,7 @@ namespace Avalonia.Styling
/// A selector match describes whether and how a <see cref="Selector"/> matches a control, and /// A selector match describes whether and how a <see cref="Selector"/> matches a control, and
/// in addition whether the selector can ever match a control of the same type. /// in addition whether the selector can ever match a control of the same type.
/// </remarks> /// </remarks>
public readonly record struct SelectorMatch internal readonly record struct SelectorMatch
{ {
/// <summary> /// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>. /// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>.

4
src/Avalonia.Base/Styling/Setter.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a /// A <see cref="Setter"/> is used to set a <see cref="AvaloniaProperty"/> value on a
/// <see cref="AvaloniaObject"/> depending on a condition. /// <see cref="AvaloniaObject"/> depending on a condition.
/// </remarks> /// </remarks>
public class Setter : ISetter, IValueEntry, ISetterInstance, IAnimationSetter public class Setter : SetterBase, IValueEntry, ISetterInstance, IAnimationSetter
{ {
private object? _value; private object? _value;
private DirectPropertySetterInstance? _direct; private DirectPropertySetterInstance? _direct;
@ -66,7 +66,7 @@ namespace Avalonia.Styling
void IValueEntry.Unsubscribe() { } void IValueEntry.Unsubscribe() { }
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)]
ISetterInstance ISetter.Instance(IStyleInstance instance, StyledElement target) internal override ISetterInstance Instance(IStyleInstance instance, StyledElement target)
{ {
if (target is not AvaloniaObject ao) if (target is not AvaloniaObject ao)
throw new InvalidOperationException("Don't know how to instance a style on this type."); throw new InvalidOperationException("Don't know how to instance a style on this type.");

12
src/Avalonia.Base/Styling/SetterBase.cs

@ -0,0 +1,12 @@
namespace Avalonia.Styling
{
/// <summary>
/// Represents the base class for value setters.
/// </summary>
public abstract class SetterBase
{
internal abstract ISetterInstance Instance(
IStyleInstance styleInstance,
StyledElement target);
}
}

6
src/Avalonia.Base/Styling/StyleBase.cs

@ -16,7 +16,7 @@ namespace Avalonia.Styling
private IResourceHost? _owner; private IResourceHost? _owner;
private StyleChildren? _children; private StyleChildren? _children;
private IResourceDictionary? _resources; private IResourceDictionary? _resources;
private List<ISetter>? _setters; private List<SetterBase>? _setters;
private List<IAnimation>? _animations; private List<IAnimation>? _animations;
private StyleInstance? _sharedInstance; private StyleInstance? _sharedInstance;
@ -60,7 +60,7 @@ namespace Avalonia.Styling
} }
} }
public IList<ISetter> Setters => _setters ??= new List<ISetter>(); public IList<SetterBase> Setters => _setters ??= new();
public IList<IAnimation> Animations => _animations ??= new List<IAnimation>(); public IList<IAnimation> Animations => _animations ??= new List<IAnimation>();
bool IResourceNode.HasResources => _resources?.Count > 0; bool IResourceNode.HasResources => _resources?.Count > 0;
@ -69,7 +69,7 @@ namespace Avalonia.Styling
internal bool HasChildren => _children?.Count > 0; internal bool HasChildren => _children?.Count > 0;
internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0; internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0;
public void Add(ISetter setter) => Setters.Add(setter); public void Add(SetterBase setter) => Setters.Add(setter);
public void Add(IStyle style) => Children.Add(style); public void Add(IStyle style) => Children.Add(style);
public event EventHandler? OwnerChanged; public event EventHandler? OwnerChanged;

12
src/Avalonia.Base/Styling/TemplateSelector.cs

@ -18,13 +18,13 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool InTemplate => true; internal override bool InTemplate => true;
/// <inheritdoc/> /// <inheritdoc/>
public override bool IsCombinator => true; internal override bool IsCombinator => true;
/// <inheritdoc/> /// <inheritdoc/>
public override Type? TargetType => null; internal override Type? TargetType => null;
public override string ToString(Style? owner) public override string ToString(Style? owner)
{ {
@ -36,7 +36,7 @@ namespace Avalonia.Styling
return _selectorString; return _selectorString;
} }
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
var templatedParent = control.TemplatedParent as StyledElement; var templatedParent = control.TemplatedParent as StyledElement;
@ -48,7 +48,7 @@ namespace Avalonia.Styling
return _parent.Match(templatedParent, parent, subscribe); return _parent.Match(templatedParent, parent, subscribe);
} }
protected override Selector? MovePrevious() => null; private protected override Selector? MovePrevious() => null;
protected override Selector? MovePreviousOrParent() => _parent; private protected override Selector? MovePreviousOrParent() => _parent;
} }
} }

12
src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs

@ -58,7 +58,7 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
public override bool InTemplate => _previous?.InTemplate ?? false; internal override bool InTemplate => _previous?.InTemplate ?? false;
/// <summary> /// <summary>
/// Gets the name of the control to match. /// Gets the name of the control to match.
@ -66,10 +66,10 @@ namespace Avalonia.Styling
public string? Name { get; set; } public string? Name { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public override Type? TargetType => _targetType ?? _previous?.TargetType; internal override Type? TargetType => _targetType ?? _previous?.TargetType;
/// <inheritdoc/> /// <inheritdoc/>
public override bool IsCombinator => false; internal override bool IsCombinator => false;
/// <summary> /// <summary>
/// Whether the selector matches the concrete <see cref="TargetType"/> or any object which /// Whether the selector matches the concrete <see cref="TargetType"/> or any object which
@ -89,7 +89,7 @@ namespace Avalonia.Styling
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe)
{ {
if (TargetType != null) if (TargetType != null)
{ {
@ -134,8 +134,8 @@ namespace Avalonia.Styling
return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance; return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance;
} }
protected override Selector? MovePrevious() => _previous; private protected override Selector? MovePrevious() => _previous;
protected override Selector? MovePreviousOrParent() => _previous; private protected override Selector? MovePreviousOrParent() => _previous;
private string BuildSelectorString(Style? owner) private string BuildSelectorString(Style? owner)
{ {

5
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3958,7 +3958,7 @@ namespace Avalonia.Controls
{ {
bool focusLeftDataGrid = true; bool focusLeftDataGrid = true;
bool dataGridWillReceiveRoutedEvent = true; bool dataGridWillReceiveRoutedEvent = true;
Visual focusedObject = FocusManager.Instance.Current as Visual; Visual focusedObject = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual;
DataGridColumn editingColumn = null; DataGridColumn editingColumn = null;
while (focusedObject != null) while (focusedObject != null)
@ -4865,7 +4865,8 @@ namespace Avalonia.Controls
if (!ctrl) if (!ctrl)
{ {
// If Enter was used by a TextBox, we shouldn't handle the key // If Enter was used by a TextBox, we shouldn't handle the key
if (FocusManager.Instance.Current is TextBox focusedTextBox && focusedTextBox.AcceptsReturn) if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is TextBox focusedTextBox
&& focusedTextBox.AcceptsReturn)
{ {
return false; return false;
} }

2
src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs

@ -695,7 +695,7 @@ namespace Avalonia.Controls
{ {
Control? focusedElement = null; Control? focusedElement = null;
if (FocusManager.Instance?.Current is Visual child) if (TopLevel.GetTopLevel(_owner)?.FocusManager?.GetFocusedElement() is Visual child)
{ {
var parent = child.GetVisualParent(); var parent = child.GetVisualParent();
var owner = _owner; var owner = _owner;

2
src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs

@ -762,7 +762,7 @@ namespace Avalonia.Controls
/// otherwise, false.</returns> /// otherwise, false.</returns>
protected bool HasFocus() protected bool HasFocus()
{ {
Visual? focused = FocusManager.Instance?.Current as Visual; Visual? focused = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual;
while (focused != null) while (focused != null)
{ {

2
src/Avalonia.Controls/Calendar/Calendar.cs

@ -1567,7 +1567,7 @@ namespace Avalonia.Controls
base.OnPointerReleased(e); base.OnPointerReleased(e);
if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left) if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left)
{ {
FocusManager.Instance?.Focus(this); Focus();
} }
} }

3
src/Avalonia.Controls/ComboBox.cs

@ -230,8 +230,7 @@ namespace Avalonia.Controls
var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
if (firstChild != null) if (firstChild != null)
{ {
FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional); e.Handled = firstChild.Focus(NavigationMethod.Directional);
e.Handled = true;
} }
} }
} }

6
src/Avalonia.Controls/ContextMenu.cs

@ -285,7 +285,7 @@ namespace Avalonia.Controls
} }
} }
void ISetterValue.Initialize(ISetter setter) void ISetterValue.Initialize(SetterBase setter)
{ {
// ContextMenu can be assigned to the ContextMenu property in a setter. This overrides // ContextMenu can be assigned to the ContextMenu property in a setter. This overrides
// the behavior defined in Control which requires controls to be wrapped in a <template>. // the behavior defined in Control which requires controls to be wrapped in a <template>.
@ -360,7 +360,7 @@ namespace Avalonia.Controls
private void PopupOpened(object? sender, EventArgs e) private void PopupOpened(object? sender, EventArgs e)
{ {
_previousFocus = FocusManager.Instance?.Current; _previousFocus = FocusManager.GetFocusManager(this)?.GetFocusedElement();
Focus(); Focus();
_popupHostChangedHandler?.Invoke(_popup!.Host); _popupHostChangedHandler?.Invoke(_popup!.Host);
@ -390,7 +390,7 @@ namespace Avalonia.Controls
} }
// HACK: Reset the focus when the popup is closed. We need to fix this so it's automatic. // HACK: Reset the focus when the popup is closed. We need to fix this so it's automatic.
FocusManager.Instance?.Focus(_previousFocus); _previousFocus?.Focus();
RaiseEvent(new RoutedEventArgs RaiseEvent(new RoutedEventArgs
{ {

2
src/Avalonia.Controls/Control.cs

@ -213,7 +213,7 @@ namespace Avalonia.Controls
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <inheritdoc/> /// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter) void ISetterValue.Initialize(SetterBase setter)
{ {
if (setter is Setter s && s.Property == ContextFlyoutProperty) if (setter is Setter s && s.Property == ContextFlyoutProperty)
{ {

11
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -323,10 +323,11 @@ namespace Avalonia.Controls
e.Handled = true; e.Handled = true;
break; break;
case Key.Tab: case Key.Tab:
if (FocusManager.Instance?.Current is IInputElement focus) var focusManager = FocusManager.GetFocusManager(this);
if (focusManager?.GetFocusedElement() is { } focus)
{ {
var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next); var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next);
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); nextFocus?.Focus(NavigationMethod.Tab);
e.Handled = true; e.Handled = true;
} }
break; break;
@ -449,15 +450,15 @@ namespace Avalonia.Controls
if (monthCol < dayCol && monthCol < yearCol) if (monthCol < dayCol && monthCol < yearCol)
{ {
KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None); _monthSelector?.Focus(NavigationMethod.Pointer);
} }
else if (dayCol < monthCol && dayCol < yearCol) else if (dayCol < monthCol && dayCol < yearCol)
{ {
KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None); _monthSelector?.Focus(NavigationMethod.Pointer);
} }
else if (yearCol < monthCol && yearCol < dayCol) else if (yearCol < monthCol && yearCol < dayCol)
{ {
KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None); _yearSelector?.Focus(NavigationMethod.Pointer);
} }
} }

6
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@ -161,10 +161,10 @@ namespace Avalonia.Controls
e.Handled = true; e.Handled = true;
break; break;
case Key.Tab: case Key.Tab:
if (FocusManager.Instance?.Current is IInputElement focus) if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is { } focus)
{ {
var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next); var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next);
KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); nextFocus?.Focus(NavigationMethod.Tab);
e.Handled = true; e.Handled = true;
} }
break; break;
@ -216,7 +216,7 @@ namespace Avalonia.Controls
_periodSelector.SelectedValue = hr >= 12 ? 1 : 0; _periodSelector.SelectedValue = hr >= 12 ? 1 : 0;
SetGrid(); SetGrid();
KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None); _hourSelector?.Focus(NavigationMethod.Pointer);
} }
private void SetGrid() private void SetGrid()

4
src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs

@ -250,14 +250,14 @@ namespace Avalonia.Controls.Primitives
// Try and focus content inside Flyout // Try and focus content inside Flyout
if (Popup.Child.Focusable) if (Popup.Child.Focusable)
{ {
FocusManager.Instance?.Focus(Popup.Child); Popup.Child.Focus();
} }
else else
{ {
var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next); var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next);
if (nextFocus != null) if (nextFocus != null)
{ {
FocusManager.Instance?.Focus(nextFocus); nextFocus.Focus();
} }
} }
} }

11
src/Avalonia.Controls/ItemsControl.cs

@ -566,19 +566,20 @@ namespace Avalonia.Controls
{ {
if (!e.Handled) if (!e.Handled)
{ {
var focus = FocusManager.Instance; var focus = FocusManager.GetFocusManager(this);
var direction = e.Key.ToNavigationDirection(); var direction = e.Key.ToNavigationDirection();
var container = Presenter?.Panel as INavigableContainer; var container = Presenter?.Panel as INavigableContainer;
if (container == null || if (focus == null ||
focus?.Current == null || container == null ||
focus.GetFocusedElement() == null ||
direction == null || direction == null ||
direction.Value.IsTab()) direction.Value.IsTab())
{ {
return; return;
} }
Visual? current = focus.Current as Visual; Visual? current = focus.GetFocusedElement() as Visual;
while (current != null) while (current != null)
{ {
@ -588,7 +589,7 @@ namespace Avalonia.Controls
if (next != null) if (next != null)
{ {
focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers); next.Focus(NavigationMethod.Directional, e.KeyModifiers);
e.Handled = true; e.Handled = true;
} }

6
src/Avalonia.Controls/Primitives/Popup.cs

@ -727,7 +727,7 @@ namespace Avalonia.Controls.Primitives
Closed?.Invoke(this, EventArgs.Empty); Closed?.Invoke(this, EventArgs.Empty);
var focusCheck = FocusManager.Instance?.Current; var focusCheck = FocusManager.GetFocusManager(this)?.GetFocusedElement();
// Focus is set to null as part of popup closing, so we only want to // Focus is set to null as part of popup closing, so we only want to
// set focus to PlacementTarget if this is the case // set focus to PlacementTarget if this is the case
@ -744,7 +744,7 @@ namespace Avalonia.Controls.Primitives
if (e is object) if (e is object)
{ {
FocusManager.Instance?.Focus(e); e.Focus();
} }
} }
else else
@ -752,7 +752,7 @@ namespace Avalonia.Controls.Primitives
var anc = this.FindLogicalAncestorOfType<Control>(); var anc = this.FindLogicalAncestorOfType<Control>();
if (anc != null) if (anc != null)
{ {
FocusManager.Instance?.Focus(anc); anc.Focus();
} }
} }
} }

5
src/Avalonia.Controls/TopLevel.cs

@ -452,6 +452,9 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>(); public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>();
/// <inheritdoc />
public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
/// <inheritdoc/> /// <inheritdoc/>
Point IRenderRoot.PointToClient(PixelPoint p) Point IRenderRoot.PointToClient(PixelPoint p)
{ {
@ -725,7 +728,7 @@ namespace Avalonia.Controls
void PlatformImpl_LostFocus() void PlatformImpl_LostFocus()
{ {
var focused = (Visual?)FocusManager.Instance?.Current; var focused = (Visual?)FocusManager?.GetFocusedElement();
if (focused == null) if (focused == null)
return; return;
while (focused.VisualParent != null) while (focused.VisualParent != null)

3
src/Avalonia.Controls/TreeView.cs

@ -560,8 +560,7 @@ namespace Avalonia.Controls
if (next != null) if (next != null)
{ {
FocusManager.Instance?.Focus(next, NavigationMethod.Directional); e.Handled = next.Focus(NavigationMethod.Directional);
e.Handled = true;
} }
} }
else else

2
src/Avalonia.Controls/TreeViewItem.cs

@ -238,7 +238,7 @@ namespace Avalonia.Controls
} }
else else
{ {
FocusManager.Instance?.Focus(treeViewItem, NavigationMethod.Directional); treeViewItem.Focus(NavigationMethod.Directional);
} }
return true; return true;

14
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -343,7 +343,7 @@ namespace Avalonia.Controls
{ {
var items = Items; var items = Items;
if (_isInLayout || index < 0 || index >= items.Count || _realizedElements is null) if (_isInLayout || index < 0 || index >= items.Count || _realizedElements is null || !IsEffectivelyVisible)
return null; return null;
if (GetRealizedElement(index) is Control element) if (GetRealizedElement(index) is Control element)
@ -662,9 +662,12 @@ namespace Avalonia.Controls
_scrollViewer?.UnregisterAnchorCandidate(element); _scrollViewer?.UnregisterAnchorCandidate(element);
var recycleKey = element.GetValue(RecycleKeyProperty); var recycleKey = element.GetValue(RecycleKeyProperty);
Debug.Assert(recycleKey is not null);
if (recycleKey == s_itemIsItsOwnContainer) if (recycleKey is null)
{
RemoveInternalChild(element);
}
else if (recycleKey == s_itemIsItsOwnContainer)
{ {
element.IsVisible = false; element.IsVisible = false;
} }
@ -687,9 +690,8 @@ namespace Avalonia.Controls
Debug.Assert(ItemContainerGenerator is not null); Debug.Assert(ItemContainerGenerator is not null);
var recycleKey = element.GetValue(RecycleKeyProperty); var recycleKey = element.GetValue(RecycleKeyProperty);
Debug.Assert(recycleKey is not null);
if (recycleKey is null || recycleKey == s_itemIsItsOwnContainer)
if (recycleKey == s_itemIsItsOwnContainer)
{ {
RemoveInternalChild(element); RemoveInternalChild(element);
} }

4
src/Avalonia.Controls/WindowBase.cs

@ -234,7 +234,7 @@ namespace Avalonia.Controls
if (this is IFocusScope scope) if (this is IFocusScope scope)
{ {
FocusManager.Instance?.RemoveFocusScope(scope); ((FocusManager?)FocusManager)?.RemoveFocusScope(scope);
} }
base.HandleClosed(); base.HandleClosed();
@ -326,7 +326,7 @@ namespace Avalonia.Controls
if (scope != null) if (scope != null)
{ {
FocusManager.Instance?.SetFocusScope(scope); ((FocusManager?)FocusManager)?.SetFocusScope(scope);
} }
IsActive = true; IsActive = true;

8
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@ -86,7 +86,7 @@ namespace Avalonia.Diagnostics
private static IDisposable Open(IDevToolsTopLevelGroup topLevelGroup, DevToolsOptions options, private static IDisposable Open(IDevToolsTopLevelGroup topLevelGroup, DevToolsOptions options,
Window? owner, Application? app) Window? owner, Application? app)
{ {
var focussedControl = KeyboardDevice.Instance?.FocusedElement as Control; var focusedControl = owner?.FocusManager?.GetFocusedElement() as Control;
AvaloniaObject root = topLevelGroup switch AvaloniaObject root = topLevelGroup switch
{ {
ClassicDesktopStyleApplicationLifetimeTopLevelGroup gr => new Controls.Application(gr, app ?? Application.Current!), ClassicDesktopStyleApplicationLifetimeTopLevelGroup gr => new Controls.Application(gr, app ?? Application.Current!),
@ -98,7 +98,7 @@ namespace Avalonia.Diagnostics
if (s_open.TryGetValue(topLevelGroup, out var mainWindow)) if (s_open.TryGetValue(topLevelGroup, out var mainWindow))
{ {
mainWindow.Activate(); mainWindow.Activate();
mainWindow.SelectedControl(focussedControl); mainWindow.SelectedControl(focusedControl);
return Disposable.Empty; return Disposable.Empty;
} }
if (topLevelGroup.Items.Count == 1 && topLevelGroup.Items is not INotifyCollectionChanged) if (topLevelGroup.Items.Count == 1 && topLevelGroup.Items is not INotifyCollectionChanged)
@ -110,7 +110,7 @@ namespace Avalonia.Diagnostics
if (group.Key.Items.Contains(singleTopLevel)) if (group.Key.Items.Contains(singleTopLevel))
{ {
group.Value.Activate(); group.Value.Activate();
group.Value.SelectedControl(focussedControl); group.Value.SelectedControl(focusedControl);
return Disposable.Empty; return Disposable.Empty;
} }
} }
@ -124,7 +124,7 @@ namespace Avalonia.Diagnostics
Tag = topLevelGroup Tag = topLevelGroup
}; };
window.SetOptions(options); window.SetOptions(options);
window.SelectedControl(focussedControl); window.SelectedControl(focusedControl);
window.Closed += DevToolsClosed; window.Closed += DevToolsClosed;
s_open.Add(topLevelGroup, window); s_open.Add(topLevelGroup, window);
if (options.ShowAsChildWindow && owner is not null) if (options.ShowAsChildWindow && owner is not null)

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs

@ -64,7 +64,7 @@ namespace Avalonia.Diagnostics.ViewModels
// We need to place styles without activator first, such styles will be overwritten by ones with activators. // We need to place styles without activator first, such styles will be overwritten by ones with activators.
foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator)) foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator))
{ {
var styleSource = appliedStyle.Source; var styleSource = appliedStyle.Style;
var setters = new List<SetterViewModel>(); var setters = new List<SetterViewModel>();

4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/StyleViewModel.cs

@ -5,11 +5,11 @@ namespace Avalonia.Diagnostics.ViewModels
{ {
internal class StyleViewModel : ViewModelBase internal class StyleViewModel : ViewModelBase
{ {
private readonly IStyleInstance _styleInstance; private readonly AppliedStyle _styleInstance;
private bool _isActive; private bool _isActive;
private bool _isVisible; private bool _isVisible;
public StyleViewModel(IStyleInstance styleInstance, string name, List<SetterViewModel> setters) public StyleViewModel(AppliedStyle styleInstance, string name, List<SetterViewModel> setters)
{ {
_styleInstance = styleInstance; _styleInstance = styleInstance;
IsVisible = true; IsVisible = true;

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -107,7 +107,7 @@ namespace Avalonia.LinuxFramebuffer
if (_topLevel is IFocusScope scope) if (_topLevel is IFocusScope scope)
{ {
FocusManager.Instance?.SetFocusScope(scope); ((FocusManager)_topLevel.FocusManager).SetFocusScope(scope);
} }
} }

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs

@ -125,7 +125,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
var selfType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().First().Type.GetClrType(); var selfType = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().First().Type.GetClrType();
// When using self bindings with setters we need to change target type to resolved selector type. // When using self bindings with setters we need to change target type to resolved selector type.
if (context.GetAvaloniaTypes().ISetter.IsAssignableFrom(selfType)) if (context.GetAvaloniaTypes().SetterBase.IsAssignableFrom(selfType))
{ {
selfType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().First().TargetType.GetClrType(); selfType = context.ParentNodes().OfType<AvaloniaXamlIlTargetTypeMetadataNode>().First().TargetType.GetClrType();
} }

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -104,7 +104,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType TextDecorationCollection { get; } public IXamlType TextDecorationCollection { get; }
public IXamlType TextDecorations { get; } public IXamlType TextDecorations { get; }
public IXamlType TextTrimming { get; } public IXamlType TextTrimming { get; }
public IXamlType ISetter { get; } public IXamlType SetterBase { get; }
public IXamlType IStyle { get; } public IXamlType IStyle { get; }
public IXamlType StyleInclude { get; } public IXamlType StyleInclude { get; }
public IXamlType ResourceInclude { get; } public IXamlType ResourceInclude { get; }
@ -244,7 +244,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
TextDecorationCollection = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorationCollection"); TextDecorationCollection = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorationCollection");
TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations"); TextDecorations = cfg.TypeSystem.GetType("Avalonia.Media.TextDecorations");
TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming"); TextTrimming = cfg.TypeSystem.GetType("Avalonia.Media.TextTrimming");
ISetter = cfg.TypeSystem.GetType("Avalonia.Styling.ISetter"); SetterBase = cfg.TypeSystem.GetType("Avalonia.Styling.SetterBase");
IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle"); IStyle = cfg.TypeSystem.GetType("Avalonia.Styling.IStyle");
StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude"); StyleInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.StyleInclude");
ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude"); ResourceInclude = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Styling.ResourceInclude");

6
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -154,7 +154,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
var namespaces = _nsInfo.XmlNamespaces; var namespaces = _nsInfo.XmlNamespaces;
if (!namespaces.TryGetValue(ns, out var lst)) if (!namespaces.TryGetValue(ns, out var lst))
throw new ArgumentException("Unable to resolve namespace for type " + qualifiedTypeName); throw new ArgumentException("Unable to resolve namespace for type " + qualifiedTypeName);
foreach (var entry in lst) var resolvable = lst.Where(static e => e.ClrAssemblyName is { Length: > 0 });
foreach (var entry in resolvable)
{ {
var asm = Assembly.Load(new AssemblyName(entry.ClrAssemblyName)); var asm = Assembly.Load(new AssemblyName(entry.ClrAssemblyName));
var resolved = asm.GetType(entry.ClrNamespace + "." + name); var resolved = asm.GetType(entry.ClrNamespace + "." + name);
@ -164,7 +165,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
throw new ArgumentException( throw new ArgumentException(
$"Unable to resolve type {qualifiedTypeName} from any of the following locations: " + $"Unable to resolve type {qualifiedTypeName} from any of the following locations: " +
string.Join(",", lst.Select(e => $"`{e.ClrAssemblyName}:{e.ClrNamespace}.{name}`"))); string.Join(",", resolvable.Select(e => $"`clr-namespace:{e.ClrNamespace};assembly={e.ClrAssemblyName}`")))
{ HelpLink = "https://docs.avaloniaui.net/guides/basics/introduction-to-xaml#valid-xaml-namespaces" };
} }
} }

2
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -109,7 +109,7 @@ namespace Avalonia.Data
} }
/// <inheritdoc/> /// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter) => _isSetterValue = true; void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true;
protected override void Subscribed() protected override void Subscribed()
{ {

33
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -7,7 +7,6 @@ using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.Utilities; using Avalonia.Rendering.Utilities;
using Avalonia.Utilities; using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using SkiaSharp; using SkiaSharp;
using ISceneBrush = Avalonia.Media.ISceneBrush; using ISceneBrush = Avalonia.Media.ISceneBrush;
@ -26,7 +25,7 @@ namespace Avalonia.Skia
private readonly Stack<double> _opacityStack = new(); private readonly Stack<double> _opacityStack = new();
private readonly Matrix? _postTransform; private readonly Matrix? _postTransform;
private double _currentOpacity = 1.0f; private double _currentOpacity = 1.0f;
private readonly bool _canTextUseLcdRendering; private readonly bool _disableSubpixelTextRendering;
private Matrix _currentTransform; private Matrix _currentTransform;
private bool _disposed; private bool _disposed;
private GRContext? _grContext; private GRContext? _grContext;
@ -59,11 +58,11 @@ namespace Avalonia.Skia
/// Dpi of drawings. /// Dpi of drawings.
/// </summary> /// </summary>
public Vector Dpi; public Vector Dpi;
/// <summary> /// <summary>
/// Render text without Lcd rendering. /// Render text without subpixel antialiasing.
/// </summary> /// </summary>
public bool DisableTextLcdRendering; public bool DisableSubpixelTextRendering;
/// <summary> /// <summary>
/// GPU-accelerated context (optional) /// GPU-accelerated context (optional)
@ -135,7 +134,7 @@ namespace Avalonia.Skia
_dpi = createInfo.Dpi; _dpi = createInfo.Dpi;
_disposables = disposables; _disposables = disposables;
_canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; _disableSubpixelTextRendering = createInfo.DisableSubpixelTextRendering;
_grContext = createInfo.GrContext; _grContext = createInfo.GrContext;
_gpu = createInfo.Gpu; _gpu = createInfo.Gpu;
if (_grContext != null) if (_grContext != null)
@ -519,7 +518,23 @@ namespace Avalonia.Skia
{ {
var glyphRunImpl = (GlyphRunImpl)glyphRun; var glyphRunImpl = (GlyphRunImpl)glyphRun;
var textBlob = glyphRunImpl.GetTextBlob(RenderOptions); var textRenderOptions = RenderOptions;
if (_disableSubpixelTextRendering)
{
switch (textRenderOptions.TextRenderingMode)
{
case TextRenderingMode.Unspecified
when textRenderOptions.EdgeMode == EdgeMode.Antialias || textRenderOptions.EdgeMode == EdgeMode.Unspecified:
case TextRenderingMode.SubpixelAntialias:
{
textRenderOptions = textRenderOptions with { TextRenderingMode = TextRenderingMode.Antialias };
break;
}
}
}
var textBlob = glyphRunImpl.GetTextBlob(textRenderOptions);
Canvas.DrawText(textBlob, (float)glyphRun.BaselineOrigin.X, Canvas.DrawText(textBlob, (float)glyphRun.BaselineOrigin.X,
(float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint); (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint);
@ -969,6 +984,7 @@ namespace Avalonia.Skia
using (var ctx = intermediate.CreateDrawingContext()) using (var ctx = intermediate.CreateDrawingContext())
{ {
ctx.RenderOptions = RenderOptions;
ctx.Clear(Colors.Transparent); ctx.Clear(Colors.Transparent);
content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y)); content.Render(ctx, rect.TopLeft == default ? null : Matrix.CreateTranslation(-rect.X, -rect.Y));
} }
@ -997,6 +1013,7 @@ namespace Avalonia.Skia
using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi); using var pictureTarget = new PictureRenderTarget(_gpu, _grContext, _dpi);
using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize)) using (var ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize))
{ {
ctx.RenderOptions = RenderOptions;
ctx.PushClip(calc.IntermediateClip); ctx.PushClip(calc.IntermediateClip);
content.Render(ctx, transform); content.Render(ctx, transform);
ctx.PopClip(); ctx.PopClip();
@ -1283,7 +1300,7 @@ namespace Avalonia.Skia
Height = pixelSize.Height, Height = pixelSize.Height,
Dpi = _dpi, Dpi = _dpi,
Format = format, Format = format,
DisableTextLcdRendering = !_canTextUseLcdRendering, DisableTextLcdRendering = isLayer ? _disableSubpixelTextRendering : true,
GrContext = _grContext, GrContext = _grContext,
Gpu = _gpu, Gpu = _gpu,
Session = _session, Session = _session,

4
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using SkiaSharp; using SkiaSharp;
namespace Avalonia.Skia namespace Avalonia.Skia
@ -54,8 +53,7 @@ namespace Avalonia.Skia
var createInfo = new DrawingContextImpl.CreateInfo var createInfo = new DrawingContextImpl.CreateInfo
{ {
Surface = _framebufferSurface, Surface = _framebufferSurface,
Dpi = framebuffer.Dpi, Dpi = framebuffer.Dpi
DisableTextLcdRendering = true
}; };
return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer); return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer);

1
src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs

@ -30,7 +30,6 @@ namespace Avalonia.Skia
GrContext = session.GrContext, GrContext = session.GrContext,
Surface = session.SkSurface, Surface = session.SkSurface,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
DisableTextLcdRendering = true,
Gpu = _skiaGpu, Gpu = _skiaGpu,
CurrentSession = session CurrentSession = session
}; };

2
src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs

@ -20,7 +20,7 @@ namespace Avalonia.Skia.Helpers
{ {
Canvas = canvas, Canvas = canvas,
Dpi = dpi, Dpi = dpi,
DisableTextLcdRendering = true, DisableSubpixelTextRendering = true,
}; };
return new DrawingContextImpl(createInfo); return new DrawingContextImpl(createInfo);

4
src/Skia/Avalonia.Skia/PictureRenderTarget.cs

@ -39,7 +39,7 @@ internal class PictureRenderTarget : IDisposable
{ {
Canvas = canvas, Canvas = canvas,
Dpi = _dpi, Dpi = _dpi,
DisableTextLcdRendering = true, DisableSubpixelTextRendering = true,
GrContext = _grContext, GrContext = _grContext,
Gpu = _gpu, Gpu = _gpu,
}; };
@ -52,4 +52,4 @@ internal class PictureRenderTarget : IDisposable
} }
public void Dispose() => _picture?.Dispose(); public void Dispose() => _picture?.Dispose();
} }

2
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -106,7 +106,7 @@ namespace Avalonia.Skia
{ {
Surface = _surface.Surface, Surface = _surface.Surface,
Dpi = Dpi, Dpi = Dpi,
DisableTextLcdRendering = _disableLcdRendering, DisableSubpixelTextRendering = _disableLcdRendering,
GrContext = _grContext, GrContext = _grContext,
Gpu = _gpu, Gpu = _gpu,
}; };

2
src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs

@ -22,7 +22,7 @@ namespace Avalonia.Win32.Embedding
UnmanagedMethods.SetParent(WindowHandle, Handle); UnmanagedMethods.SetParent(WindowHandle, Handle);
_root.Prepare(); _root.Prepare();
if (_root.IsFocused) if (_root.IsFocused)
FocusManager.Instance.Focus(null); _root.FocusManager.ClearFocus();
_root.GotFocus += RootGotFocus; _root.GotFocus += RootGotFocus;
FixPosition(); FixPosition();

2
src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs

@ -5,7 +5,7 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32.Input namespace Avalonia.Win32.Input
{ {
class WindowsKeyboardDevice : KeyboardDevice internal class WindowsKeyboardDevice : KeyboardDevice
{ {
private readonly byte[] _keyStates = new byte[256]; private readonly byte[] _keyStates = new byte[256];

58
tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs

@ -23,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus(); target.Focus();
Assert.Same(target, FocusManager.Instance.Current); Assert.Same(target, root.FocusManager.GetFocusedElement());
} }
} }
@ -39,14 +39,14 @@ namespace Avalonia.Base.UnitTests.Input
Child = target = new Button() { IsVisible = false} Child = target = new Button() { IsVisible = false}
}; };
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus(); target.Focus();
Assert.False(target.IsFocused); Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin); Assert.False(target.IsKeyboardFocusWithin);
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -67,14 +67,14 @@ namespace Avalonia.Base.UnitTests.Input
} }
}; };
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus(); target.Focus();
Assert.False(target.IsFocused); Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin); Assert.False(target.IsKeyboardFocusWithin);
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -100,11 +100,11 @@ namespace Avalonia.Base.UnitTests.Input
first.Focus(); first.Focus();
Assert.Same(first, FocusManager.Instance.Current); Assert.Same(first, root.FocusManager.GetFocusedElement());
second.Focus(); second.Focus();
Assert.Same(first, FocusManager.Instance.Current); Assert.Same(first, root.FocusManager.GetFocusedElement());
} }
} }
@ -120,14 +120,14 @@ namespace Avalonia.Base.UnitTests.Input
Child = target = new Button() { IsEnabled = false } Child = target = new Button() { IsEnabled = false }
}; };
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus(); target.Focus();
Assert.False(target.IsFocused); Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin); Assert.False(target.IsKeyboardFocusWithin);
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -148,14 +148,14 @@ namespace Avalonia.Base.UnitTests.Input
} }
}; };
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
target.Focus(); target.Focus();
Assert.False(target.IsFocused); Assert.False(target.IsFocused);
Assert.False(target.IsKeyboardFocusWithin); Assert.False(target.IsKeyboardFocusWithin);
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -201,7 +201,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus(); target.Focus();
target.IsVisible = false; target.IsVisible = false;
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -224,7 +224,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus(); target.Focus();
container.IsVisible = false; container.IsVisible = false;
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -243,7 +243,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus(); target.Focus();
target.IsEnabled = false; target.IsEnabled = false;
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -266,7 +266,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus(); target.Focus();
container.IsEnabled = false; container.IsEnabled = false;
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -285,7 +285,7 @@ namespace Avalonia.Base.UnitTests.Input
target.Focus(); target.Focus();
root.Child = null; root.Child = null;
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
@ -312,13 +312,13 @@ namespace Avalonia.Base.UnitTests.Input
target2.ApplyTemplate(); target2.ApplyTemplate();
FocusManager.Instance?.Focus(target1); target1.Focus();
Assert.True(target1.IsFocused); Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus")); Assert.True(target1.Classes.Contains(":focus"));
Assert.False(target2.IsFocused); Assert.False(target2.IsFocused);
Assert.False(target2.Classes.Contains(":focus")); Assert.False(target2.Classes.Contains(":focus"));
FocusManager.Instance?.Focus(target2, NavigationMethod.Tab); target2.Focus(NavigationMethod.Tab);
Assert.False(target1.IsFocused); Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus")); Assert.False(target1.Classes.Contains(":focus"));
Assert.True(target2.IsFocused); Assert.True(target2.IsFocused);
@ -348,19 +348,19 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate(); target1.ApplyTemplate();
target2.ApplyTemplate(); target2.ApplyTemplate();
FocusManager.Instance?.Focus(target1); target1.Focus();
Assert.True(target1.IsFocused); Assert.True(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-visible")); Assert.False(target1.Classes.Contains(":focus-visible"));
Assert.False(target2.IsFocused); Assert.False(target2.IsFocused);
Assert.False(target2.Classes.Contains(":focus-visible")); Assert.False(target2.Classes.Contains(":focus-visible"));
FocusManager.Instance?.Focus(target2, NavigationMethod.Tab); target2.Focus(NavigationMethod.Tab);
Assert.False(target1.IsFocused); Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-visible")); Assert.False(target1.Classes.Contains(":focus-visible"));
Assert.True(target2.IsFocused); Assert.True(target2.IsFocused);
Assert.True(target2.Classes.Contains(":focus-visible")); Assert.True(target2.Classes.Contains(":focus-visible"));
FocusManager.Instance?.Focus(target1, NavigationMethod.Directional); target1.Focus(NavigationMethod.Directional);
Assert.True(target1.IsFocused); Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-visible")); Assert.True(target1.Classes.Contains(":focus-visible"));
Assert.False(target2.IsFocused); Assert.False(target2.IsFocused);
@ -390,7 +390,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate(); target1.ApplyTemplate();
target2.ApplyTemplate(); target2.ApplyTemplate();
FocusManager.Instance?.Focus(target1); target1.Focus();
Assert.True(target1.IsFocused); Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin); Assert.True(target1.IsKeyboardFocusWithin);
@ -425,7 +425,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate(); target1.ApplyTemplate();
target2.ApplyTemplate(); target2.ApplyTemplate();
FocusManager.Instance?.Focus(target1); target1.Focus();
Assert.True(target1.IsFocused); Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin); Assert.True(target1.IsKeyboardFocusWithin);
@ -436,7 +436,7 @@ namespace Avalonia.Base.UnitTests.Input
Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.Classes.Contains(":focus-within"));
Assert.True(root.IsKeyboardFocusWithin); Assert.True(root.IsKeyboardFocusWithin);
FocusManager.Instance?.Focus(target2); target2.Focus();
Assert.False(target1.IsFocused); Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.Classes.Contains(":focus-within"));
@ -478,7 +478,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate(); target1.ApplyTemplate();
target2.ApplyTemplate(); target2.ApplyTemplate();
FocusManager.Instance?.Focus(target1); target1.Focus();
Assert.True(target1.IsFocused); Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin); Assert.True(target1.IsKeyboardFocusWithin);
@ -534,7 +534,7 @@ namespace Avalonia.Base.UnitTests.Input
target1.ApplyTemplate(); target1.ApplyTemplate();
target2.ApplyTemplate(); target2.ApplyTemplate();
FocusManager.Instance?.Focus(target1); target1.Focus();
Assert.True(target1.IsFocused); Assert.True(target1.IsFocused);
Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.Classes.Contains(":focus-within"));
Assert.True(target1.IsKeyboardFocusWithin); Assert.True(target1.IsKeyboardFocusWithin);
@ -545,7 +545,7 @@ namespace Avalonia.Base.UnitTests.Input
Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1); Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1);
FocusManager.Instance?.Focus(target2); target2.Focus();
Assert.False(target1.IsFocused); Assert.False(target1.IsFocused);
Assert.False(target1.Classes.Contains(":focus-within")); Assert.False(target1.Classes.Contains(":focus-within"));
@ -578,9 +578,9 @@ namespace Avalonia.Base.UnitTests.Input
}; };
target.Focus(); target.Focus();
FocusManager.Instance.Focus(null); root.FocusManager.ClearFocus();
Assert.Null(FocusManager.Instance.Current); Assert.Null(root.FocusManager.GetFocusedElement());
} }
} }
} }

4
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@ -20,7 +20,9 @@ namespace Avalonia.Base.UnitTests.Input
[Fact] [Fact]
public void Close_Should_Remove_PointerOver() public void Close_Should_Remove_PointerOver()
{ {
using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); using var app = UnitTestApplication.Start(new TestServices(
inputManager: new InputManager(),
focusManager: new FocusManager()));
var renderer = RendererMocks.CreateRenderer(); var renderer = RendererMocks.CreateRenderer();
var device = CreatePointerDeviceMock().Object; var device = CreatePointerDeviceMock().Object;

36
tests/Avalonia.Benchmarks/Styling/SelectorBenchmark.cs

@ -37,62 +37,62 @@ namespace Avalonia.Benchmarks.Styling
} }
[Benchmark] [Benchmark]
public SelectorMatch IsSelector_NoMatch() public void IsSelector_NoMatch()
{ {
return _isCalendarSelector.Match(_notMatchingControl); _isCalendarSelector.Match(_notMatchingControl);
} }
[Benchmark] [Benchmark]
public SelectorMatch IsSelector_Match() public void IsSelector_Match()
{ {
return _isCalendarSelector.Match(_matchingControl); _isCalendarSelector.Match(_matchingControl);
} }
[Benchmark] [Benchmark]
public SelectorMatch ClassSelector_NoMatch() public void ClassSelector_NoMatch()
{ {
return _classSelector.Match(_notMatchingControl); _classSelector.Match(_notMatchingControl);
} }
[Benchmark] [Benchmark]
public SelectorMatch ClassSelector_Match() public void ClassSelector_Match()
{ {
return _classSelector.Match(_matchingControl); _classSelector.Match(_matchingControl);
} }
[Benchmark] [Benchmark]
public SelectorMatch OrSelector_One_Match() public void OrSelector_One_Match()
{ {
return _orSelectorTwo.Match(_matchingControl); _orSelectorTwo.Match(_matchingControl);
} }
[Benchmark] [Benchmark]
public SelectorMatch OrSelector_Five_Match() public void OrSelector_Five_Match()
{ {
return _orSelectorFive.Match(_matchingControl); _orSelectorFive.Match(_matchingControl);
} }
} }
internal class AlwaysMatchSelector : Selector internal class AlwaysMatchSelector : Selector
{ {
public override bool InTemplate => false; internal override bool InTemplate => false;
public override bool IsCombinator => false; internal override bool IsCombinator => false;
public override Type TargetType => null; internal override Type TargetType => null;
public override string ToString(Style owner) public override string ToString(Style owner)
{ {
return "Always"; return "Always";
} }
protected override SelectorMatch Evaluate(StyledElement control, IStyle parent, bool subscribe) private protected override SelectorMatch Evaluate(StyledElement control, IStyle parent, bool subscribe)
{ {
return SelectorMatch.AlwaysThisType; return SelectorMatch.AlwaysThisType;
} }
protected override Selector MovePrevious() => null; private protected override Selector MovePrevious() => null;
protected override Selector MovePreviousOrParent() => null; private protected override Selector MovePreviousOrParent() => null;
} }
} }

10
tests/Avalonia.Controls.UnitTests/FlyoutTests.cs

@ -288,10 +288,10 @@ namespace Avalonia.Controls.UnitTests
window.Show(); window.Show();
button.Focus(); button.Focus();
Assert.True(FocusManager.Instance?.Current == button); Assert.True(window.FocusManager.GetFocusedElement() == button);
button.Flyout.ShowAt(button); button.Flyout.ShowAt(button);
Assert.False(button.IsFocused); Assert.False(button.IsFocused);
Assert.True(FocusManager.Instance?.Current == flyoutTextBox); Assert.True(window.FocusManager.GetFocusedElement() == flyoutTextBox);
} }
} }
@ -322,10 +322,10 @@ namespace Avalonia.Controls.UnitTests
window.Content = button; window.Content = button;
window.Show(); window.Show();
FocusManager.Instance?.Focus(button); button.Focus();
Assert.True(FocusManager.Instance?.Current == button); Assert.True(window.FocusManager.GetFocusedElement() == button);
button.Flyout.ShowAt(button); button.Flyout.ShowAt(button);
Assert.True(FocusManager.Instance?.Current == button); Assert.True(window.FocusManager.GetFocusedElement() == button);
} }
} }

6
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -576,8 +576,9 @@ namespace Avalonia.Controls.UnitTests
}); });
var panel = Assert.IsAssignableFrom<Panel>(target.ItemsPanelRoot); var panel = Assert.IsAssignableFrom<Panel>(target.ItemsPanelRoot);
var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager;
Assert.Equal(panel.Children[1], FocusManager.Instance!.Current); Assert.Equal(panel.Children[1], focusManager?.GetFocusedElement());
} }
[Fact] [Fact]
@ -601,8 +602,9 @@ namespace Avalonia.Controls.UnitTests
}); });
var panel = Assert.IsAssignableFrom<Panel>(target.ItemsPanelRoot); var panel = Assert.IsAssignableFrom<Panel>(target.ItemsPanelRoot);
var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager;
Assert.Equal(panel.Children[2], FocusManager.Instance!.Current); Assert.Equal(panel.Children[2], focusManager?.GetFocusedElement());
} }
[Fact] [Fact]

8
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -979,7 +979,7 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab); RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(0); var item = target.ContainerFromIndex(0);
Assert.Same(item, FocusManager.Instance.Current); Assert.Same(item, root.FocusManager.GetFocusedElement());
} }
[Fact] [Fact]
@ -1026,17 +1026,17 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab); RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(1); var item = target.ContainerFromIndex(1);
Assert.Same(item, FocusManager.Instance.Current); Assert.Same(item, root.FocusManager.GetFocusedElement());
RaiseKeyEvent(item, Key.Tab); RaiseKeyEvent(item, Key.Tab);
Assert.Same(button, FocusManager.Instance.Current); Assert.Same(button, root.FocusManager.GetFocusedElement());
target.Selection.AnchorIndex = 2; target.Selection.AnchorIndex = 2;
RaiseKeyEvent(button, Key.Tab); RaiseKeyEvent(button, Key.Tab);
item = target.ContainerFromIndex(2); item = target.ContainerFromIndex(2);
Assert.Same(item, FocusManager.Instance.Current); Assert.Same(item, root.FocusManager.GetFocusedElement());
} }
private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0) private static void RaiseKeyEvent(Control target, Key key, KeyModifiers inputModifiers = 0)

4
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@ -267,7 +267,7 @@ namespace Avalonia.Controls.UnitTests.Platform
target.KeyDown(item.Object, e); target.KeyDown(item.Object, e);
parentItem.Verify(x => x.Close()); parentItem.Verify(x => x.Close());
parentItem.Verify(x => x.Focus()); parentItem.Verify(x => x.Focus(It.IsAny<NavigationMethod>(), It.IsAny<KeyModifiers>()));
Assert.True(e.Handled); Assert.True(e.Handled);
} }
@ -351,7 +351,7 @@ namespace Avalonia.Controls.UnitTests.Platform
target.KeyDown(item.Object, e); target.KeyDown(item.Object, e);
parentItem.Verify(x => x.Close()); parentItem.Verify(x => x.Close());
parentItem.Verify(x => x.Focus()); parentItem.Verify(x => x.Focus(It.IsAny<NavigationMethod>(), It.IsAny<KeyModifiers>()));
Assert.True(e.Handled); Assert.True(e.Handled);
} }

11
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -642,10 +642,11 @@ namespace Avalonia.Controls.UnitTests.Primitives
tb.Focus(); tb.Focus();
Assert.True(FocusManager.Instance?.Current == tb); var focusManager = TopLevel.GetTopLevel(tb)!.FocusManager;
tb = Assert.IsType<TextBox>(focusManager.GetFocusedElement());
//Ensure focus remains in the popup //Ensure focus remains in the popup
var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next); var nextFocus = KeyboardNavigationHandler.GetNext(tb, NavigationDirection.Next);
Assert.True(nextFocus == b); Assert.True(nextFocus == b);
@ -684,7 +685,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
p.Close(); p.Close();
var focus = FocusManager.Instance?.Current; var focusManager = window.FocusManager;
var focus = focusManager.GetFocusedElement();
Assert.True(focus == window); Assert.True(focus == window);
} }
} }
@ -723,7 +725,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
windowTB.Focus(); windowTB.Focus();
var focus = FocusManager.Instance?.Current; var focusManager = window.FocusManager;
var focus = focusManager.GetFocusedElement();
Assert.True(focus == windowTB); Assert.True(focus == windowTB);

8
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -461,7 +461,7 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab); RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(0); var item = target.ContainerFromIndex(0);
Assert.Same(item, FocusManager.Instance.Current); Assert.Same(item, root.FocusManager.GetFocusedElement());
} }
[Fact] [Fact]
@ -513,17 +513,17 @@ namespace Avalonia.Controls.UnitTests
RaiseKeyEvent(button, Key.Tab); RaiseKeyEvent(button, Key.Tab);
var item = target.ContainerFromIndex(1); var item = target.ContainerFromIndex(1);
Assert.Same(item, FocusManager.Instance.Current); Assert.Same(item, root.FocusManager.GetFocusedElement());
RaiseKeyEvent(item, Key.Tab); RaiseKeyEvent(item, Key.Tab);
Assert.Same(button, FocusManager.Instance.Current); Assert.Same(button, root.FocusManager.GetFocusedElement());
target.Selection.AnchorIndex = 2; target.Selection.AnchorIndex = 2;
RaiseKeyEvent(button, Key.Tab); RaiseKeyEvent(button, Key.Tab);
item = target.ContainerFromIndex(2); item = target.ContainerFromIndex(2);
Assert.Same(item, FocusManager.Instance.Current); Assert.Same(item, root.FocusManager.GetFocusedElement());
} }
private static IControlTemplate TabControlTemplate() private static IControlTemplate TabControlTemplate()

16
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -969,7 +969,6 @@ namespace Avalonia.Controls.UnitTests
public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node() public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node()
{ {
using var app = Start(); using var app = Start();
var focus = FocusManager.Instance!;
var navigation = AvaloniaLocator.Current.GetRequiredService<IKeyboardNavigationHandler>(); var navigation = AvaloniaLocator.Current.GetRequiredService<IKeyboardNavigationHandler>();
var data = CreateTestTreeData(); var data = CreateTestTreeData();
@ -984,6 +983,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Children = { target, button }, Children = { target, button },
}); });
var focus = root.FocusManager;
root.LayoutManager.ExecuteInitialLayoutPass(); root.LayoutManager.ExecuteInitialLayoutPass();
ExpandAll(target); ExpandAll(target);
@ -994,20 +994,19 @@ namespace Avalonia.Controls.UnitTests
target.SelectedItem = item; target.SelectedItem = item;
node.Focus(); node.Focus();
Assert.Same(node, focus.Current); Assert.Same(node, focus.GetFocusedElement());
navigation.Move(focus.Current!, NavigationDirection.Next); navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
Assert.Same(button, focus.Current); Assert.Same(button, focus.GetFocusedElement());
navigation.Move(focus.Current!, NavigationDirection.Next); navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next);
Assert.Same(node, focus.Current); Assert.Same(node, focus.GetFocusedElement());
} }
[Fact] [Fact]
public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree() public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree()
{ {
using var app = Start(); using var app = Start();
var focus = FocusManager.Instance!;
var data = CreateTestTreeData(); var data = CreateTestTreeData();
var selectedNode = new Node { Value = "Out of Tree Selected Item" }; var selectedNode = new Node { Value = "Out of Tree Selected Item" };
@ -1025,6 +1024,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Children = { target, button }, Children = { target, button },
}); });
var focus = root.FocusManager;
root.LayoutManager.ExecuteInitialLayoutPass(); root.LayoutManager.ExecuteInitialLayoutPass();
ExpandAll(target); ExpandAll(target);
@ -1035,7 +1035,7 @@ namespace Avalonia.Controls.UnitTests
target.SelectedItem = selectedNode; target.SelectedItem = selectedNode;
node.Focus(); node.Focus();
Assert.Same(node, focus.Current); Assert.Same(node, focus.GetFocusedElement());
} }
[Fact] [Fact]

92
tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs

@ -646,7 +646,7 @@ namespace Avalonia.Controls.UnitTests
{ {
// Issue #11272 // Issue #11272
using var app = App(); using var app = App();
var (_, _, itemsControl) = CreateUnrootedTarget(); var (_, _, itemsControl) = CreateUnrootedTarget<ItemsControl>();
var container = new Decorator { Margin = new Thickness(100) }; var container = new Decorator { Margin = new Thickness(100) };
var root = new TestRoot(true, container); var root = new TestRoot(true, container);
@ -657,6 +657,78 @@ namespace Avalonia.Controls.UnitTests
root.LayoutManager.ExecuteLayoutPass(); root.LayoutManager.ExecuteLayoutPass();
} }
[Fact]
public void Supports_Null_Recycle_Key_When_Scrolling()
{
using var app = App();
var (_, scroll, itemsControl) = CreateUnrootedTarget<NonRecyclingItemsControl>();
var root = CreateRoot(itemsControl);
root.LayoutManager.ExecuteInitialLayoutPass();
var firstItem = itemsControl.ContainerFromIndex(0)!;
scroll.Offset = new(0, 20);
Layout(itemsControl);
Assert.Null(firstItem.Parent);
Assert.Null(firstItem.VisualParent);
Assert.DoesNotContain(firstItem, itemsControl.ItemsPanelRoot!.Children);
}
[Fact]
public void Supports_Null_Recycle_Key_When_Clearing_Items()
{
using var app = App();
var (_, _, itemsControl) = CreateUnrootedTarget<NonRecyclingItemsControl>();
var root = CreateRoot(itemsControl);
root.LayoutManager.ExecuteInitialLayoutPass();
var firstItem = itemsControl.ContainerFromIndex(0)!;
itemsControl.ItemsSource = null;
Layout(itemsControl);
Assert.Null(firstItem.Parent);
Assert.Null(firstItem.VisualParent);
Assert.Empty(itemsControl.ItemsPanelRoot!.Children);
}
[Fact]
public void ScrollIntoView_On_Effectively_Invisible_Panel_Does_Not_Create_Ghost_Elements()
{
var items = new[] { "foo", "bar", "baz" };
var (target, _, itemsControl) = CreateUnrootedTarget<ItemsControl>(items: items);
var container = new Decorator { Margin = new Thickness(100), Child = itemsControl };
var root = new TestRoot(true, container);
root.LayoutManager.ExecuteInitialLayoutPass();
// Clear the items and do a layout to recycle all elements.
itemsControl.ItemsSource = null;
root.LayoutManager.ExecuteLayoutPass();
// Should have no realized elements and 3 unrealized elements.
Assert.Equal(0, target.GetRealizedElements().Count);
Assert.Equal(3, target.Children.Count);
// Make the panel effectively invisible and set items.
container.IsVisible = false;
itemsControl.ItemsSource = items;
// Try to scroll into view while effectively invisible.
target.ScrollIntoView(0);
// Make the panel visible and layout.
container.IsVisible = true;
root.LayoutManager.ExecuteLayoutPass();
// Should have 3 realized elements and no unrealized elements.
Assert.Equal(3, target.GetRealizedElements().Count);
Assert.Equal(3, target.Children.Count);
}
private static IReadOnlyList<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl) private static IReadOnlyList<int> GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl)
{ {
return target.GetRealizedElements() return target.GetRealizedElements()
@ -704,7 +776,7 @@ namespace Avalonia.Controls.UnitTests
Optional<IDataTemplate?> itemTemplate = default, Optional<IDataTemplate?> itemTemplate = default,
IEnumerable<Style>? styles = null) IEnumerable<Style>? styles = null)
{ {
var (target, scroll, itemsControl) = CreateUnrootedTarget(items, itemTemplate); var (target, scroll, itemsControl) = CreateUnrootedTarget<ItemsControl>(items, itemTemplate);
var root = CreateRoot(itemsControl, styles); var root = CreateRoot(itemsControl, styles);
root.LayoutManager.ExecuteInitialLayoutPass(); root.LayoutManager.ExecuteInitialLayoutPass();
@ -712,9 +784,10 @@ namespace Avalonia.Controls.UnitTests
return (target, scroll, itemsControl); return (target, scroll, itemsControl);
} }
private static (VirtualizingStackPanel, ScrollViewer, ItemsControl) CreateUnrootedTarget( private static (VirtualizingStackPanel, ScrollViewer, T) CreateUnrootedTarget<T>(
IEnumerable<object>? items = null, IEnumerable<object>? items = null,
Optional<IDataTemplate?> itemTemplate = default) Optional<IDataTemplate?> itemTemplate = default)
where T : ItemsControl, new()
{ {
var target = new VirtualizingStackPanel(); var target = new VirtualizingStackPanel();
@ -732,7 +805,7 @@ namespace Avalonia.Controls.UnitTests
Template = ScrollViewerTemplate(), Template = ScrollViewerTemplate(),
}; };
var itemsControl = new ItemsControl var itemsControl = new T
{ {
ItemsSource = items, ItemsSource = items,
Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)), Template = new FuncControlTemplate<ItemsControl>((_, ns) => scroll.RegisterInNameScope(ns)),
@ -806,5 +879,16 @@ namespace Avalonia.Controls.UnitTests
public event NotifyCollectionChangedEventHandler? CollectionChanged; public event NotifyCollectionChangedEventHandler? CollectionChanged;
} }
private class NonRecyclingItemsControl : ItemsControl
{
protected override Type StyleKeyOverride => typeof(ItemsControl);
protected internal override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
recycleKey = null;
return true;
}
}
} }
} }

4
tests/Avalonia.LeakTests/ControlTests.cs

@ -561,7 +561,7 @@ namespace Avalonia.LeakTests
var window = new Window { Focusable = true }; var window = new Window { Focusable = true };
window.Show(); window.Show();
Assert.Same(window, FocusManager.Instance.Current); Assert.Same(window, window.FocusManager.GetFocusedElement());
// Context menu in resources means the baseline may not be 0. // Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0; var initialMenuCount = 0;
@ -608,7 +608,7 @@ namespace Avalonia.LeakTests
var window = new Window { Focusable = true }; var window = new Window { Focusable = true };
window.Show(); window.Show();
Assert.Same(window, FocusManager.Instance.Current); Assert.Same(window, window.FocusManager.GetFocusedElement());
// Context menu in resources means the baseline may not be 0. // Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0; var initialMenuCount = 0;

4
tests/Avalonia.UnitTests/StyleHelpers.cs

@ -6,9 +6,9 @@ namespace Avalonia.UnitTests
{ {
public static class StyleHelpers public static class StyleHelpers
{ {
public static SelectorMatchResult TryAttach(Style style, StyledElement element, object? host = null) public static void TryAttach(Style style, StyledElement element, object? host = null)
{ {
return style.TryAttach(element, host ?? element, PropertyStore.FrameType.Style); style.TryAttach(element, host ?? element, PropertyStore.FrameType.Style);
} }
} }
} }

1
tests/Avalonia.UnitTests/TestRoot.cs

@ -54,6 +54,7 @@ namespace Avalonia.UnitTests
public IAccessKeyHandler AccessKeyHandler => null; public IAccessKeyHandler AccessKeyHandler => null;
public IKeyboardNavigationHandler KeyboardNavigationHandler => null; public IKeyboardNavigationHandler KeyboardNavigationHandler => null;
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
public IInputElement PointerOverElement { get; set; } public IInputElement PointerOverElement { get; set; }

Loading…
Cancel
Save