diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 8ac2fb28ed..8f1b39ae12 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all diff --git a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs index 7fb5bec589..aef82768c8 100644 --- a/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Linq; using System.Reflection; using Avalonia.Controls; @@ -23,7 +22,7 @@ namespace ControlCatalog.Pages $"Text was dragged {++textCount} times"), DragDropEffects.Copy | DragDropEffects.Move | DragDropEffects.Link); SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"), DragDropEffects.Move); - SetupDnd("Files", d => d.Set(DataFormats.Files, new[] { Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName }), DragDropEffects.Copy); + SetupDnd("Files", async d => d.Set(DataFormats.Files, new[] { await (VisualRoot as TopLevel)!.StorageProvider.TryGetFileFromPathAsync(Assembly.GetEntryAssembly()?.GetModules().FirstOrDefault()?.FullyQualifiedName) }), DragDropEffects.Copy); } void SetupDnd(string suffix, Action factory, DragDropEffects effects) @@ -99,7 +98,7 @@ namespace ControlCatalog.Pages { if (item is IStorageFile file) { - var content = await DialogsPage.ReadTextFromFile(file, 1000); + var content = await DialogsPage.ReadTextFromFile(file, 500); contentStr += $"File {item.Name}:{Environment.NewLine}{content}{Environment.NewLine}{Environment.NewLine}"; } else if (item is IStorageFolder folder) diff --git a/samples/GpuInterop/VulkanDemo/VulkanContext.cs b/samples/GpuInterop/VulkanDemo/VulkanContext.cs index 1d44549089..a810a4b9f7 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanContext.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanContext.cs @@ -174,7 +174,7 @@ public unsafe class VulkanContext : IDisposable for (uint queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; queueFamilyIndex++) { var family = familyProperties[queueFamilyIndex]; - if (!family.QueueFlags.HasAllFlags(QueueFlags.GraphicsBit)) + if (!family.QueueFlags.HasFlag(QueueFlags.GraphicsBit)) continue; diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index d8d9678a2d..1c88f2c95f 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -47,6 +47,9 @@ + diff --git a/samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs b/samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs index 98ab91b0a6..748654594b 100644 --- a/samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs +++ b/samples/VirtualizationDemo/ViewModels/PlaygroundPageViewModel.cs @@ -24,19 +24,19 @@ public class PlaygroundPageViewModel : ViewModelBase public bool Multiple { - get => _selectionMode.HasAnyFlag(SelectionMode.Multiple); + get => _selectionMode.HasFlag(SelectionMode.Multiple); set => SetSelectionMode(SelectionMode.Multiple, value); } public bool Toggle { - get => _selectionMode.HasAnyFlag(SelectionMode.Toggle); + get => _selectionMode.HasFlag(SelectionMode.Toggle); set => SetSelectionMode(SelectionMode.Toggle, value); } public bool AlwaysSelected { - get => _selectionMode.HasAnyFlag(SelectionMode.AlwaysSelected); + get => _selectionMode.HasFlag(SelectionMode.AlwaysSelected); set => SetSelectionMode(SelectionMode.AlwaysSelected, value); } diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index 5208c8b218..98d4a71415 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/src/Avalonia.Base/Animation/Animatable.cs @@ -58,7 +58,7 @@ namespace Avalonia.Animation /// This method should not be called from user code, it will be called automatically by the framework /// when a control is added to the visual tree. /// - protected void EnableTransitions() + internal void EnableTransitions() { if (!_transitionsEnabled) { @@ -83,7 +83,7 @@ namespace Avalonia.Animation /// This method should not be called from user code, it will be called automatically by the framework /// when a control is removed from the visual tree. /// - protected void DisableTransitions() + internal void DisableTransitions() { if (_transitionsEnabled) { diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index eafff3b780..ddc2b7effb 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -63,6 +63,7 @@ + diff --git a/src/Avalonia.Base/AvaloniaPropertyExtensions.cs b/src/Avalonia.Base/AvaloniaPropertyExtensions.cs index 6aaa224b00..2c005637dc 100644 --- a/src/Avalonia.Base/AvaloniaPropertyExtensions.cs +++ b/src/Avalonia.Base/AvaloniaPropertyExtensions.cs @@ -7,7 +7,7 @@ namespace Avalonia /// /// Extensions for . /// - public static class AvaloniaPropertyExtensions + internal static class AvaloniaPropertyExtensions { /// /// Checks if values of given property can affect rendering (via ). diff --git a/src/Avalonia.Base/Collections/Pooled/ClearMode.cs b/src/Avalonia.Base/Collections/Pooled/ClearMode.cs index d78ac8feab..518542bf07 100644 --- a/src/Avalonia.Base/Collections/Pooled/ClearMode.cs +++ b/src/Avalonia.Base/Collections/Pooled/ClearMode.cs @@ -9,7 +9,7 @@ namespace Avalonia.Collections.Pooled /// what each option does before using anything other than the default /// of Auto. /// - public enum ClearMode + internal enum ClearMode { /// /// Auto has different behavior depending on the host project's target framework. diff --git a/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs b/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs index 7a233a62ab..88f022f114 100644 --- a/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs +++ b/src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs @@ -11,7 +11,7 @@ namespace Avalonia.Collections.Pooled /// /// The type of elements in the read-only pooled list. - public interface IReadOnlyPooledList : IReadOnlyList + internal interface IReadOnlyPooledList : IReadOnlyList { #pragma warning disable CS0419 /// diff --git a/src/Avalonia.Base/Collections/Pooled/PooledList.cs b/src/Avalonia.Base/Collections/Pooled/PooledList.cs index 150fe5f7a9..73e1b28798 100644 --- a/src/Avalonia.Base/Collections/Pooled/PooledList.cs +++ b/src/Avalonia.Base/Collections/Pooled/PooledList.cs @@ -29,7 +29,7 @@ namespace Avalonia.Collections.Pooled [DebuggerDisplay("Count = {Count}")] [DebuggerTypeProxy(typeof(ICollectionDebugView<>))] [Serializable] - public class PooledList : IList, IReadOnlyPooledList, IList, IDisposable, IDeserializationCallback + internal class PooledList : IList, IReadOnlyPooledList, IList, IDisposable, IDeserializationCallback { // internal constant copied from Array.MaxArrayLength private const int MaxArrayLength = 0x7FEFFFFF; diff --git a/src/Avalonia.Base/Collections/Pooled/PooledStack.cs b/src/Avalonia.Base/Collections/Pooled/PooledStack.cs index 3d29c43051..7a8a6ff67b 100644 --- a/src/Avalonia.Base/Collections/Pooled/PooledStack.cs +++ b/src/Avalonia.Base/Collections/Pooled/PooledStack.cs @@ -29,7 +29,7 @@ namespace Avalonia.Collections.Pooled [DebuggerTypeProxy(typeof(StackDebugView<>))] [DebuggerDisplay("Count = {Count}")] [Serializable] - public class PooledStack : IEnumerable, ICollection, IReadOnlyCollection, IDisposable, IDeserializationCallback + internal class PooledStack : IEnumerable, ICollection, IReadOnlyCollection, IDisposable, IDeserializationCallback { [NonSerialized] private ArrayPool _pool; diff --git a/src/Avalonia.Base/Controls/ChildNameScope.cs b/src/Avalonia.Base/Controls/ChildNameScope.cs index 1ceaff924c..eeca120973 100644 --- a/src/Avalonia.Base/Controls/ChildNameScope.cs +++ b/src/Avalonia.Base/Controls/ChildNameScope.cs @@ -3,7 +3,7 @@ using Avalonia.Utilities; namespace Avalonia.Controls { - public class ChildNameScope : INameScope + internal class ChildNameScope : INameScope { private readonly INameScope _parentScope; private readonly NameScope _inner = new NameScope(); diff --git a/src/Avalonia.Base/Data/Core/CommonPropertyNames.cs b/src/Avalonia.Base/Data/Core/CommonPropertyNames.cs index da6f407d81..14659fe01c 100644 --- a/src/Avalonia.Base/Data/Core/CommonPropertyNames.cs +++ b/src/Avalonia.Base/Data/Core/CommonPropertyNames.cs @@ -1,6 +1,6 @@ namespace Avalonia.Data.Core { - public static class CommonPropertyNames + internal static class CommonPropertyNames { public const string IndexerName = "Item"; } diff --git a/src/Avalonia.Base/Data/IndexerBinding.cs b/src/Avalonia.Base/Data/IndexerBinding.cs index 83ef8f76b4..a1b9e8b151 100644 --- a/src/Avalonia.Base/Data/IndexerBinding.cs +++ b/src/Avalonia.Base/Data/IndexerBinding.cs @@ -2,7 +2,7 @@ namespace Avalonia.Data { - public class IndexerBinding : IBinding + internal class IndexerBinding : IBinding { public IndexerBinding( AvaloniaObject source, diff --git a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs index 75c1e3432b..671bbfbdfc 100644 --- a/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs +++ b/src/Avalonia.Base/Diagnostics/IAvaloniaObjectDebug.cs @@ -5,7 +5,7 @@ namespace Avalonia.Diagnostics /// /// Provides a debug interface into . /// - public interface IAvaloniaObjectDebug + internal interface IAvaloniaObjectDebug { /// /// Gets the subscriber list for the diff --git a/src/Avalonia.Base/Diagnostics/INotifyCollectionChangedDebug.cs b/src/Avalonia.Base/Diagnostics/INotifyCollectionChangedDebug.cs index 00f2949b08..f43e17325b 100644 --- a/src/Avalonia.Base/Diagnostics/INotifyCollectionChangedDebug.cs +++ b/src/Avalonia.Base/Diagnostics/INotifyCollectionChangedDebug.cs @@ -8,7 +8,7 @@ namespace Avalonia.Diagnostics /// Provides a debug interface into subscribers on /// /// - public interface INotifyCollectionChangedDebug + internal interface INotifyCollectionChangedDebug { /// /// Gets the subscriber list for the diff --git a/src/Avalonia.Base/EnumExtensions.cs b/src/Avalonia.Base/EnumExtensions.cs index 9b74266b09..c2b29c6620 100644 --- a/src/Avalonia.Base/EnumExtensions.cs +++ b/src/Avalonia.Base/EnumExtensions.cs @@ -6,7 +6,7 @@ namespace Avalonia /// /// Provides extension methods for enums. /// - public static class EnumExtensions + internal static class EnumExtensions { [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs index 2bd9fce947..2b8786089f 100644 --- a/src/Avalonia.Base/Input/AccessKeyHandler.cs +++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs @@ -9,7 +9,7 @@ namespace Avalonia.Input /// /// Handles access keys for a window. /// - public class AccessKeyHandler : IAccessKeyHandler + internal class AccessKeyHandler : IAccessKeyHandler { /// /// Defines the AccessKeyPressed attached event. @@ -141,9 +141,11 @@ namespace Avalonia.Input 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. // 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 // access key markers in the window (i.e. "_File"). diff --git a/src/Avalonia.Base/Input/FocusManager.cs b/src/Avalonia.Base/Input/FocusManager.cs index c8de7267ca..c3316eb278 100644 --- a/src/Avalonia.Base/Input/FocusManager.cs +++ b/src/Avalonia.Base/Input/FocusManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using Avalonia.Interactivity; +using Avalonia.Metadata; using Avalonia.VisualTree; namespace Avalonia.Input @@ -10,6 +11,7 @@ namespace Avalonia.Input /// /// Manages focus for the application. /// + [PrivateApi] public class FocusManager : IFocusManager { /// @@ -29,15 +31,12 @@ namespace Avalonia.Input RoutingStrategies.Tunnel); } - /// - /// Gets the instance of the . - /// - public static IFocusManager? Instance => AvaloniaLocator.Current.GetService(); + private IInputElement? Current => KeyboardDevice.Instance?.FocusedElement; /// /// Gets the currently focused . /// - public IInputElement? Current => KeyboardDevice.Instance?.FocusedElement; + public IInputElement? GetFocusedElement() => Current; /// /// Gets the current focus scope. @@ -54,7 +53,7 @@ namespace Avalonia.Input /// The control to focus. /// The method by which focus was changed. /// Any key modifiers active at the time of focus. - public void Focus( + public bool Focus( IInputElement? control, NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None) @@ -67,7 +66,7 @@ namespace Avalonia.Input if (scope != null) { Scope = scope; - SetFocusedElement(scope, control, method, keyModifiers); + return SetFocusedElement(scope, control, method, keyModifiers); } } else if (Current != null) @@ -79,28 +78,29 @@ namespace Avalonia.Input _focusScopes.TryGetValue(scope, out var element) && element != null) { - Focus(element, method); - return; + return Focus(element, method); } } if (Scope is object) { // 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) - { - _focusScopes.TryGetValue(scope, out var result); - return result; - } + Focus(null); + } - return null; + public IInputElement? GetFocusedElement(IFocusScope scope) + { + _focusScopes.TryGetValue(scope, out var result); + return result; } /// @@ -114,7 +114,7 @@ namespace Avalonia.Input /// If the specified scope is the current then the keyboard focus /// will change. /// - public void SetFocusedElement( + public bool SetFocusedElement( IFocusScope scope, IInputElement? element, NavigationMethod method = NavigationMethod.Unspecified, @@ -124,7 +124,7 @@ namespace Avalonia.Input if (element is not null && !CanFocus(element)) { - return; + return false; } if (_focusScopes.TryGetValue(scope, out var existingElement)) @@ -144,6 +144,8 @@ namespace Avalonia.Input { KeyboardDevice.Instance?.SetFocusedElement(element, method, keyModifiers); } + + return true; } /// @@ -185,6 +187,20 @@ namespace Avalonia.Input public static bool GetIsFocusScope(IInputElement e) => e is IFocusScope; + /// + /// 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. + /// + 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(); + } + /// /// Checks if the specified element can be focused. /// @@ -237,7 +253,7 @@ namespace Avalonia.Input { if (element is IInputElement inputElement && CanFocus(inputElement)) { - Instance?.Focus(inputElement, NavigationMethod.Pointer, ev.KeyModifiers); + inputElement.Focus(NavigationMethod.Pointer, ev.KeyModifiers); break; } diff --git a/src/Avalonia.Base/Input/IFocusManager.cs b/src/Avalonia.Base/Input/IFocusManager.cs index 0c85cad2f7..5691172f3f 100644 --- a/src/Avalonia.Base/Input/IFocusManager.cs +++ b/src/Avalonia.Base/Input/IFocusManager.cs @@ -11,40 +11,12 @@ namespace Avalonia.Input /// /// Gets the currently focused . /// - IInputElement? Current { get; } + IInputElement? GetFocusedElement(); /// - /// Gets the current focus scope. + /// Clears currently focused element. /// - IFocusScope? Scope { get; } - - /// - /// Focuses a control. - /// - /// The control to focus. - /// The method by which focus was changed. - /// Any key modifiers active at the time of focus. - void Focus( - IInputElement? control, - NavigationMethod method = NavigationMethod.Unspecified, - KeyModifiers keyModifiers = KeyModifiers.None); - - /// - /// Notifies the focus manager of a change in focus scope. - /// - /// The new focus scope. - /// - /// This should not be called by client code. It is called by an - /// when it activates, e.g. when a Window is activated. - /// - void SetFocusScope(IFocusScope scope); - - /// - /// Notifies the focus manager that a focus scope has been removed. - /// - /// The focus scope to be removed. - /// This should not be called by client code. It is called by an - /// when it deactivates or closes, e.g. when a Window is closed. - void RemoveFocusScope(IFocusScope scope); + [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(); } } diff --git a/src/Avalonia.Base/Input/IFocusScope.cs b/src/Avalonia.Base/Input/IFocusScope.cs index 56f558040e..4f7c454263 100644 --- a/src/Avalonia.Base/Input/IFocusScope.cs +++ b/src/Avalonia.Base/Input/IFocusScope.cs @@ -1,5 +1,8 @@ +using Avalonia.Metadata; + namespace Avalonia.Input { + [NotClientImplementable] public interface IFocusScope { } diff --git a/src/Avalonia.Base/Input/IInputElement.cs b/src/Avalonia.Base/Input/IInputElement.cs index 6c20d20b4d..39dc30befd 100644 --- a/src/Avalonia.Base/Input/IInputElement.cs +++ b/src/Avalonia.Base/Input/IInputElement.cs @@ -119,7 +119,9 @@ namespace Avalonia.Input /// /// Focuses the control. /// - void Focus(); + /// The method by which focus was changed. + /// Any key modifiers active at the time of focus. + bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None); /// /// Gets the key bindings for the element. diff --git a/src/Avalonia.Base/Input/IInputRoot.cs b/src/Avalonia.Base/Input/IInputRoot.cs index 344a4eefd7..0100b22ba7 100644 --- a/src/Avalonia.Base/Input/IInputRoot.cs +++ b/src/Avalonia.Base/Input/IInputRoot.cs @@ -18,6 +18,14 @@ namespace Avalonia.Input /// IKeyboardNavigationHandler KeyboardNavigationHandler { get; } + /// + /// Gets focus manager of the root. + /// + /// + /// Focus manager can be null only if application wasn't initialized yet. + /// + IFocusManager? FocusManager { get; } + /// /// Gets or sets the input element that the pointer is currently over. /// diff --git a/src/Avalonia.Base/Input/IKeyboardDevice.cs b/src/Avalonia.Base/Input/IKeyboardDevice.cs index 0b7b5aaecc..172b58068c 100644 --- a/src/Avalonia.Base/Input/IKeyboardDevice.cs +++ b/src/Avalonia.Base/Input/IKeyboardDevice.cs @@ -44,13 +44,7 @@ namespace Avalonia.Input } [NotClientImplementable] - public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged + public interface IKeyboardDevice : IInputDevice { - IInputElement? FocusedElement { get; } - - void SetFocusedElement( - IInputElement? element, - NavigationMethod method, - KeyModifiers modifiers); } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 33ddbaedf9..68131e5bf7 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -458,9 +458,10 @@ namespace Avalonia.Input SetAndRaise(IsEffectivelyEnabledProperty, ref _isEffectivelyEnabled, 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 => _gestureRecognizers ?? (_gestureRecognizers = new GestureRecognizerCollection(this)); - /// - /// Focuses the control. - /// - public void Focus() + /// + public bool Focus(NavigationMethod method = NavigationMethod.Unspecified, KeyModifiers keyModifiers = KeyModifiers.None) { - FocusManager.Instance?.Focus(this); + return FocusManager.GetFocusManager(this)?.Focus(this, method, keyModifiers) ?? false; } /// @@ -506,7 +505,7 @@ namespace Avalonia.Input if (IsFocused) { - FocusManager.Instance?.Focus(null); + FocusManager.GetFocusManager(this)?.ClearFocus(); } } @@ -649,7 +648,7 @@ namespace Avalonia.Input } else if (change.Property == IsVisibleProperty && !change.GetNewValue() && IsFocused) { - FocusManager.Instance?.Focus(null); + FocusManager.GetFocusManager(this)?.ClearFocus(); } } diff --git a/src/Avalonia.Base/Input/KeyboardDevice.cs b/src/Avalonia.Base/Input/KeyboardDevice.cs index c46834fff4..a81bc6b2e0 100644 --- a/src/Avalonia.Base/Input/KeyboardDevice.cs +++ b/src/Avalonia.Base/Input/KeyboardDevice.cs @@ -3,9 +3,11 @@ using System.Runtime.CompilerServices; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Interactivity; +using Avalonia.Metadata; namespace Avalonia.Input { + [PrivateApi] public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged { private IInputElement? _focusedElement; @@ -13,7 +15,7 @@ namespace Avalonia.Input public event PropertyChangedEventHandler? PropertyChanged; - public static IKeyboardDevice? Instance => AvaloniaLocator.Current.GetService(); + internal static KeyboardDevice? Instance => AvaloniaLocator.Current.GetService() as KeyboardDevice; public IInputManager? InputManager => AvaloniaLocator.Current.GetService(); diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs index ba909de60f..7a83501ce4 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using Avalonia.Input.Navigation; +using Avalonia.Metadata; using Avalonia.VisualTree; namespace Avalonia.Input @@ -8,6 +9,7 @@ namespace Avalonia.Input /// /// Handles keyboard navigation for a window. /// + [Unstable] public class KeyboardNavigationHandler : IKeyboardNavigationHandler { /// @@ -75,20 +77,23 @@ namespace Avalonia.Input /// The direction to move. /// Any key modifiers active at the time of focus. public void Move( - IInputElement element, + IInputElement? element, NavigationDirection direction, KeyModifiers keyModifiers = KeyModifiers.None) { - element = element ?? throw new ArgumentNullException(nameof(element)); + if (element is null && _owner is null) + { + return; + } - var next = GetNext(element, direction); + var next = GetNext(element ?? _owner!, direction); if (next != null) { var method = direction == NavigationDirection.Next || direction == NavigationDirection.Previous ? NavigationMethod.Tab : NavigationMethod.Directional; - FocusManager.Instance?.Focus(next, method, keyModifiers); + next.Focus(method, keyModifiers); } } @@ -99,10 +104,9 @@ namespace Avalonia.Input /// The event args. protected virtual void OnKeyDown(object? sender, KeyEventArgs e) { - var current = FocusManager.Instance?.Current; - - if (current != null && e.Key == Key.Tab) + if (e.Key == Key.Tab) { + var current = FocusManager.GetFocusManager(e.Source as IInputElement)?.GetFocusedElement(); var direction = (e.KeyModifiers & KeyModifiers.Shift) == 0 ? NavigationDirection.Next : NavigationDirection.Previous; Move(current, direction, e.KeyModifiers); diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 44412cd152..f3e77433a9 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Avalonia.Reactive; using Avalonia.Input.Raw; +using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Utilities; #pragma warning disable CS0618 @@ -11,6 +12,7 @@ namespace Avalonia.Input /// /// Represents a mouse device. /// + [PrivateApi] public class MouseDevice : IMouseDevice, IDisposable { private int _clickCount; diff --git a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs index c460ecf3b3..9697e32926 100644 --- a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs @@ -190,9 +190,11 @@ namespace Avalonia.Input.Navigation private static IInputElement? FocusedElement(IInputElement? e) { // 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 (!IsFocusScope(e)) diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index b3cd39212b..832f32fb03 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Avalonia.Input.Raw; +using Avalonia.Metadata; using Avalonia.Platform; #pragma warning disable CS0618 @@ -11,6 +12,7 @@ namespace Avalonia.Input /// /// Represents a pen/stylus device. /// + [PrivateApi] public class PenDevice : IPenDevice, IDisposable { private readonly Dictionary _pointers = new(); diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index bab1b9f784..78b570da14 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Avalonia.Input.Raw; +using Avalonia.Metadata; using Avalonia.Platform; #pragma warning disable CS0618 @@ -14,6 +15,7 @@ namespace Avalonia.Input /// /// This class is supposed to be used on per-toplevel basis, don't use a shared one /// + [PrivateApi] public class TouchDevice : IPointerDevice, IDisposable { private readonly Dictionary _pointers = new Dictionary(); diff --git a/src/Avalonia.Base/Interactivity/Interactive.cs b/src/Avalonia.Base/Interactivity/Interactive.cs index 821e00d784..0dfaae0fc1 100644 --- a/src/Avalonia.Base/Interactivity/Interactive.cs +++ b/src/Avalonia.Base/Interactivity/Interactive.cs @@ -17,7 +17,7 @@ namespace Avalonia.Interactivity /// /// Gets the interactive parent of the object for bubbling and tunneling events. /// - protected internal virtual Interactive? InteractiveParent => VisualParent as Interactive; + internal virtual Interactive? InteractiveParent => VisualParent as Interactive; /// /// Adds a handler for the specified routed event. diff --git a/src/Avalonia.Base/Logging/TraceLogSink.cs b/src/Avalonia.Base/Logging/TraceLogSink.cs index a1b4dfe3aa..1fee35bf21 100644 --- a/src/Avalonia.Base/Logging/TraceLogSink.cs +++ b/src/Avalonia.Base/Logging/TraceLogSink.cs @@ -6,7 +6,7 @@ using Avalonia.Utilities; namespace Avalonia.Logging { - public class TraceLogSink : ILogSink + internal class TraceLogSink : ILogSink { private readonly LogEventLevel _level; private readonly IList? _areas; diff --git a/src/Avalonia.Base/Media/ArcSegment.cs b/src/Avalonia.Base/Media/ArcSegment.cs index b7dbd4925b..ee353b0a89 100644 --- a/src/Avalonia.Base/Media/ArcSegment.cs +++ b/src/Avalonia.Base/Media/ArcSegment.cs @@ -95,7 +95,7 @@ namespace Avalonia.Media set { SetValue(SweepDirectionProperty, value); } } - protected internal override void ApplyTo(StreamGeometryContext ctx) + internal override void ApplyTo(StreamGeometryContext ctx) { ctx.ArcTo(Point, Size, RotationAngle, IsLargeArc, SweepDirection); } diff --git a/src/Avalonia.Base/Media/BezierSegment .cs b/src/Avalonia.Base/Media/BezierSegment .cs index 64ea2924cc..31efe1ec23 100644 --- a/src/Avalonia.Base/Media/BezierSegment .cs +++ b/src/Avalonia.Base/Media/BezierSegment .cs @@ -56,7 +56,7 @@ namespace Avalonia.Media set { SetValue(Point3Property, value); } } - protected internal override void ApplyTo(StreamGeometryContext ctx) + internal override void ApplyTo(StreamGeometryContext ctx) { ctx.CubicBezierTo(Point1, Point2, Point3); } diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index fc31077221..fbe9370edc 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -173,7 +173,7 @@ namespace Avalonia.Media.Imaging public virtual PixelFormat? Format => (PlatformImpl.Item as IReadableBitmapImpl)?.Format; - protected internal unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride, + private protected unsafe void CopyPixelsCore(PixelRect sourceRect, IntPtr buffer, int bufferSize, int stride, ILockedFramebuffer fb) { if ((sourceRect.Width <= 0 || sourceRect.Height <= 0) && (sourceRect.X != 0 || sourceRect.Y != 0)) diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs index 2781141f06..df6d14ecf9 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableTileBrush.cs @@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable /// How the source rectangle will be stretched to fill the destination rect. /// /// The tile mode. - protected internal ImmutableTileBrush( + private protected ImmutableTileBrush( AlignmentX alignmentX, AlignmentY alignmentY, RelativeRect destinationRect, diff --git a/src/Avalonia.Base/Media/LineSegment.cs b/src/Avalonia.Base/Media/LineSegment.cs index 5729ab2c3b..68193bb770 100644 --- a/src/Avalonia.Base/Media/LineSegment.cs +++ b/src/Avalonia.Base/Media/LineSegment.cs @@ -22,7 +22,7 @@ namespace Avalonia.Media set { SetValue(PointProperty, value); } } - protected internal override void ApplyTo(StreamGeometryContext ctx) + internal override void ApplyTo(StreamGeometryContext ctx) { ctx.LineTo(Point); } diff --git a/src/Avalonia.Base/Media/PathSegment.cs b/src/Avalonia.Base/Media/PathSegment.cs index 89a33815ae..0b517e56f3 100644 --- a/src/Avalonia.Base/Media/PathSegment.cs +++ b/src/Avalonia.Base/Media/PathSegment.cs @@ -2,6 +2,6 @@ namespace Avalonia.Media { public abstract class PathSegment : AvaloniaObject { - protected internal abstract void ApplyTo(StreamGeometryContext ctx); + internal abstract void ApplyTo(StreamGeometryContext ctx); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Media/PolyLineSegment.cs b/src/Avalonia.Base/Media/PolyLineSegment.cs index 51bf13d7cb..49c34fea66 100644 --- a/src/Avalonia.Base/Media/PolyLineSegment.cs +++ b/src/Avalonia.Base/Media/PolyLineSegment.cs @@ -44,7 +44,7 @@ namespace Avalonia.Media Points = new Points(points); } - protected internal override void ApplyTo(StreamGeometryContext ctx) + internal override void ApplyTo(StreamGeometryContext ctx) { var points = Points; if (points.Count > 0) diff --git a/src/Avalonia.Base/Media/QuadraticBezierSegment .cs b/src/Avalonia.Base/Media/QuadraticBezierSegment .cs index 9dc24e2a93..01d22f2043 100644 --- a/src/Avalonia.Base/Media/QuadraticBezierSegment .cs +++ b/src/Avalonia.Base/Media/QuadraticBezierSegment .cs @@ -40,7 +40,7 @@ namespace Avalonia.Media set { SetValue(Point2Property, value); } } - protected internal override void ApplyTo(StreamGeometryContext ctx) + internal override void ApplyTo(StreamGeometryContext ctx) { ctx.QuadraticBezierTo(Point1, Point2); } diff --git a/src/Avalonia.Base/Media/StreamGeometryContext.cs b/src/Avalonia.Base/Media/StreamGeometryContext.cs index ed6065eae4..8b51bf3676 100644 --- a/src/Avalonia.Base/Media/StreamGeometryContext.cs +++ b/src/Avalonia.Base/Media/StreamGeometryContext.cs @@ -10,7 +10,6 @@ namespace Avalonia.Media /// of is obtained by calling /// . /// - /// TODO: This class is just a wrapper around IStreamGeometryContextImpl: is it needed? public class StreamGeometryContext : IGeometryContext { private readonly IStreamGeometryContextImpl _impl; diff --git a/src/Avalonia.Base/Media/Transformation/TransformParser.cs b/src/Avalonia.Base/Media/Transformation/TransformParser.cs index 85f4f5fec1..cfbc5b23c4 100644 --- a/src/Avalonia.Base/Media/Transformation/TransformParser.cs +++ b/src/Avalonia.Base/Media/Transformation/TransformParser.cs @@ -4,7 +4,7 @@ using Avalonia.Utilities; namespace Avalonia.Media.Transformation { - public static class TransformParser + internal static class TransformParser { private static readonly (string, TransformFunction)[] s_functionMapping = { diff --git a/src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs b/src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs deleted file mode 100644 index 4a0d0c634f..0000000000 --- a/src/Avalonia.Base/Platform/Storage/NameCollisionOption.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Avalonia.Platform.Storage; - -public class NameCollisionOption -{ - -} diff --git a/src/Avalonia.Base/Rendering/ManagedDeferredRendererLock.cs b/src/Avalonia.Base/Rendering/ManagedDeferredRendererLock.cs index 158fecbfc6..0397a32bfd 100644 --- a/src/Avalonia.Base/Rendering/ManagedDeferredRendererLock.cs +++ b/src/Avalonia.Base/Rendering/ManagedDeferredRendererLock.cs @@ -4,7 +4,7 @@ using Avalonia.Utilities; namespace Avalonia.Rendering { - public class ManagedDeferredRendererLock : DisposableLock, IDeferredRendererLock + internal class ManagedDeferredRendererLock : DisposableLock, IDeferredRendererLock { } diff --git a/src/Avalonia.Base/Rendering/RendererBase.cs b/src/Avalonia.Base/Rendering/RendererBase.cs deleted file mode 100644 index f0ddbd3626..0000000000 --- a/src/Avalonia.Base/Rendering/RendererBase.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using Avalonia.Media; - -namespace Avalonia.Rendering -{ - public class RendererBase - { - private readonly bool _useManualFpsCounting; - private static int s_fontSize = 18; - private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); - private int _framesThisSecond; - private int _fps; - private TimeSpan _lastFpsUpdate; - - public RendererBase(bool useManualFpsCounting = false) - { - _useManualFpsCounting = useManualFpsCounting; - } - - protected void FpsTick() => _framesThisSecond++; - - protected void RenderFps(DrawingContext context, Rect clientRect, int? layerCount) - { - var now = _stopwatch.Elapsed; - var elapsed = now - _lastFpsUpdate; - - if (!_useManualFpsCounting) - ++_framesThisSecond; - - if (elapsed.TotalSeconds > 1) - { - _fps = (int)(_framesThisSecond / elapsed.TotalSeconds); - _framesThisSecond = 0; - _lastFpsUpdate = now; - } - - var text = layerCount.HasValue ? FormattableString.Invariant($"Layers: {layerCount} FPS: {_fps:000}") : FormattableString.Invariant($"FPS: {_fps:000}"); - - var formattedText = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Typeface.Default, s_fontSize, Brushes.White); - - var rect = new Rect(clientRect.Right - formattedText.Width, 0, formattedText.Width, formattedText.Height); - - context.DrawRectangle(Brushes.Black, null, rect); - - context.DrawText(formattedText, rect.TopLeft); - } - } -} diff --git a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs index 6b34b44337..439bda4494 100644 --- a/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs +++ b/src/Avalonia.Base/Rendering/Utilities/TileBrushCalculator.cs @@ -2,7 +2,7 @@ namespace Avalonia.Rendering.Utilities { - public class TileBrushCalculator + internal class TileBrushCalculator { private readonly Size _imageSize; private readonly Rect _drawRect; diff --git a/src/Avalonia.Base/Rendering/ZIndexComparer.cs b/src/Avalonia.Base/Rendering/ZIndexComparer.cs index c9240668b4..8009dc8573 100644 --- a/src/Avalonia.Base/Rendering/ZIndexComparer.cs +++ b/src/Avalonia.Base/Rendering/ZIndexComparer.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using Avalonia.VisualTree; namespace Avalonia.Rendering { - public class ZIndexComparer : IComparer + internal class ZIndexComparer : IComparer { public static readonly ZIndexComparer Instance = new ZIndexComparer(); public static readonly Comparison ComparisonInstance = Instance.Compare; diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 1a0b3bcea6..b98e378338 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -556,7 +556,7 @@ namespace Avalonia /// Notifies child controls that a change has been made to resources that apply to them. /// /// The event args. - protected virtual void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) + internal virtual void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { if (_logicalChildren is object) { diff --git a/src/Avalonia.Base/Utilities/DisposableLock.cs b/src/Avalonia.Base/Utilities/DisposableLock.cs index c6f01b24fa..3202a7610d 100644 --- a/src/Avalonia.Base/Utilities/DisposableLock.cs +++ b/src/Avalonia.Base/Utilities/DisposableLock.cs @@ -3,7 +3,7 @@ using System.Threading; namespace Avalonia.Utilities { - public class DisposableLock + internal class DisposableLock { private readonly object _lock = new object(); diff --git a/src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs b/src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs index 55fd9a7957..61ce6e7b71 100644 --- a/src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs +++ b/src/Avalonia.Base/Utilities/NonPumpingLockHelper.cs @@ -3,7 +3,7 @@ using Avalonia.Threading; namespace Avalonia.Utilities { - public class NonPumpingLockHelper + internal class NonPumpingLockHelper { public interface IHelperImpl { diff --git a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs index 0eb7ae2e31..5edddc1a6d 100644 --- a/src/Avalonia.Base/Utilities/SingleOrDictionary.cs +++ b/src/Avalonia.Base/Utilities/SingleOrDictionary.cs @@ -11,7 +11,7 @@ namespace Avalonia.Utilities /// /// The type of the key. /// The type of the value. - public class SingleOrDictionary : IEnumerable> + internal class SingleOrDictionary : IEnumerable> where TKey : notnull { private KeyValuePair? _singleValue; diff --git a/src/Avalonia.Base/Utilities/SingleOrQueue.cs b/src/Avalonia.Base/Utilities/SingleOrQueue.cs index 99a92f6788..2a608dbbfd 100644 --- a/src/Avalonia.Base/Utilities/SingleOrQueue.cs +++ b/src/Avalonia.Base/Utilities/SingleOrQueue.cs @@ -7,7 +7,7 @@ namespace Avalonia.Utilities /// FIFO Queue optimized for holding zero or one items. /// /// The type of items held in the queue. - public class SingleOrQueue + internal class SingleOrQueue { private T? _head; private Queue? _tail; diff --git a/src/Avalonia.Base/Utilities/ValueSingleOrList.cs b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs index c1e5d912a8..22afa39067 100644 --- a/src/Avalonia.Base/Utilities/ValueSingleOrList.cs +++ b/src/Avalonia.Base/Utilities/ValueSingleOrList.cs @@ -9,7 +9,7 @@ namespace Avalonia.Utilities /// /// Once more than value has been added to this storage it will switch to using internally. /// - public ref struct ValueSingleOrList + internal ref struct ValueSingleOrList { private bool _isSingleSet; diff --git a/src/Avalonia.Base/Utilities/WeakTimer.cs b/src/Avalonia.Base/Utilities/WeakTimer.cs deleted file mode 100644 index ab784fca4d..0000000000 --- a/src/Avalonia.Base/Utilities/WeakTimer.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using Avalonia.Threading; - -namespace Avalonia.Utilities -{ - public class WeakTimer - { - public interface IWeakTimerSubscriber - { - bool Tick(); - } - - private readonly WeakReference _subscriber; - private DispatcherTimer _timer; - - public WeakTimer(IWeakTimerSubscriber subscriber) - { - _subscriber = new WeakReference(subscriber); - _timer = new DispatcherTimer(); - - _timer.Tick += delegate { OnTick(); }; - } - - private void OnTick() - { - if (!_subscriber.TryGetTarget(out var subscriber) || !subscriber.Tick()) - Stop(); - } - - public TimeSpan Interval - { - get { return _timer.Interval; } - set { _timer.Interval = value; } - } - - public void Start() => _timer.Start(); - - public void Stop() => _timer.Stop(); - - - public static WeakTimer StartWeakTimer(IWeakTimerSubscriber subscriber, TimeSpan interval) - { - var timer = new WeakTimer(subscriber) {Interval = interval}; - timer.Start(); - return timer; - } - - } -} diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 30c89d186f..50b23f158d 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -321,7 +321,7 @@ namespace Avalonia internal RenderOptions RenderOptions { get; set; } - public bool HasNonUniformZIndexChildren { get; private set; } + internal bool HasNonUniformZIndexChildren { get; private set; } /// /// Gets a value indicating whether this control is attached to a visual root. diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index a55a47fa53..bfcd4750e3 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3958,7 +3958,7 @@ namespace Avalonia.Controls { bool focusLeftDataGrid = true; bool dataGridWillReceiveRoutedEvent = true; - Visual focusedObject = FocusManager.Instance.Current as Visual; + Visual focusedObject = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual; DataGridColumn editingColumn = null; while (focusedObject != null) @@ -4865,7 +4865,8 @@ namespace Avalonia.Controls if (!ctrl) { // 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; } diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs index 49c8b9ff92..d03949c1c2 100644 --- a/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs @@ -53,8 +53,8 @@ namespace Avalonia.Controls { return _owner.GetElementImpl( index, - options.HasAllFlags(ElementRealizationOptions.ForceCreate), - options.HasAllFlags(ElementRealizationOptions.SuppressAutoRecycle)); + options.HasFlag(ElementRealizationOptions.ForceCreate), + options.HasFlag(ElementRealizationOptions.SuppressAutoRecycle)); } protected override object GetItemAtCore(int index) => _owner.ItemsSourceView!.GetAt(index)!; diff --git a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs index 6b9d7934bf..674389d77c 100644 --- a/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs @@ -695,7 +695,7 @@ namespace Avalonia.Controls { Control? focusedElement = null; - if (FocusManager.Instance?.Current is Visual child) + if (TopLevel.GetTopLevel(_owner)?.FocusManager?.GetFocusedElement() is Visual child) { var parent = child.GetVisualParent(); var owner = _owner; diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 20711eecbc..d0b894101f 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -762,7 +762,7 @@ namespace Avalonia.Controls /// otherwise, false. protected bool HasFocus() { - Visual? focused = FocusManager.Instance?.Current as Visual; + Visual? focused = FocusManager.GetFocusManager(this)?.GetFocusedElement() as Visual; while (focused != null) { diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index d04dfec3e8..c55bd0f3e5 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -151,7 +151,7 @@ namespace Avalonia.Automation.Peers protected override bool HasKeyboardFocusCore() => Owner.IsFocused; protected override bool IsContentElementCore() => true; protected override bool IsControlElementCore() => true; - protected override bool IsEnabledCore() => Owner.IsEnabled; + protected override bool IsEnabledCore() => Owner.IsEffectivelyEnabled; protected override bool IsKeyboardFocusableCore() => Owner.Focusable; protected override void SetFocusCore() => Owner.Focus(); diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 10aadfa759..6468d0b4e8 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -1567,7 +1567,7 @@ namespace Avalonia.Controls base.OnPointerReleased(e); if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left) { - FocusManager.Instance?.Focus(this); + Focus(); } } diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index f41c00662d..efa319ad54 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -230,8 +230,7 @@ namespace Avalonia.Controls var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c)); if (firstChild != null) { - FocusManager.Instance?.Focus(firstChild, NavigationMethod.Directional); - e.Handled = true; + e.Handled = firstChild.Focus(NavigationMethod.Directional); } } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 39a98bd48a..a9d8e4c9c7 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -360,7 +360,7 @@ namespace Avalonia.Controls private void PopupOpened(object? sender, EventArgs e) { - _previousFocus = FocusManager.Instance?.Current; + _previousFocus = FocusManager.GetFocusManager(this)?.GetFocusedElement(); Focus(); _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. - FocusManager.Instance?.Focus(_previousFocus); + _previousFocus?.Focus(); RaiseEvent(new RoutedEventArgs { diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index 0ae743f30a..fb61ea679c 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -323,10 +323,11 @@ namespace Avalonia.Controls e.Handled = true; break; 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); - KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); + nextFocus?.Focus(NavigationMethod.Tab); e.Handled = true; } break; @@ -449,15 +450,15 @@ namespace Avalonia.Controls if (monthCol < dayCol && monthCol < yearCol) { - KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None); + _monthSelector?.Focus(NavigationMethod.Pointer); } else if (dayCol < monthCol && dayCol < yearCol) { - KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None); + _monthSelector?.Focus(NavigationMethod.Pointer); } else if (yearCol < monthCol && yearCol < dayCol) { - KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None); + _yearSelector?.Focus(NavigationMethod.Pointer); } } diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs index ba06e1b5e6..929ad68c24 100644 --- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs @@ -161,10 +161,10 @@ namespace Avalonia.Controls e.Handled = true; break; case Key.Tab: - if (FocusManager.Instance?.Current is IInputElement focus) + if (FocusManager.GetFocusManager(this)?.GetFocusedElement() is { } focus) { var nextFocus = KeyboardNavigationHandler.GetNext(focus, NavigationDirection.Next); - KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None); + nextFocus?.Focus(NavigationMethod.Tab); e.Handled = true; } break; @@ -216,7 +216,7 @@ namespace Avalonia.Controls _periodSelector.SelectedValue = hr >= 12 ? 1 : 0; SetGrid(); - KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None); + _hourSelector?.Focus(NavigationMethod.Pointer); } private void SetGrid() diff --git a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs index 5b23b5030f..7fd9fad605 100644 --- a/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs +++ b/src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs @@ -250,14 +250,14 @@ namespace Avalonia.Controls.Primitives // Try and focus content inside Flyout if (Popup.Child.Focusable) { - FocusManager.Instance?.Focus(Popup.Child); + Popup.Child.Focus(); } else { var nextFocus = KeyboardNavigationHandler.GetNext(Popup.Child, NavigationDirection.Next); if (nextFocus != null) { - FocusManager.Instance?.Focus(nextFocus); + nextFocus.Focus(); } } } diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 300c66d126..06427991f8 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -590,19 +590,20 @@ namespace Avalonia.Controls { if (!e.Handled) { - var focus = FocusManager.Instance; + var focus = FocusManager.GetFocusManager(this); var direction = e.Key.ToNavigationDirection(); var container = Presenter?.Panel as INavigableContainer; - if (container == null || - focus?.Current == null || + if (focus == null || + container == null || + focus.GetFocusedElement() == null || direction == null || direction.Value.IsTab()) { return; } - Visual? current = focus.Current as Visual; + Visual? current = focus.GetFocusedElement() as Visual; while (current != null) { @@ -612,7 +613,7 @@ namespace Avalonia.Controls if (next != null) { - focus.Focus(next, NavigationMethod.Directional, e.KeyModifiers); + next.Focus(NavigationMethod.Directional, e.KeyModifiers); e.Handled = true; } diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 1704965fd7..3ee50ab547 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -302,7 +302,7 @@ namespace Avalonia.Controls.Presenters /// /// This method is automatically called when the control is attached to a visual tree. /// - protected internal virtual void AttachToScrollViewer() + internal void AttachToScrollViewer() { var owner = this.FindAncestorOfType(); diff --git a/src/Avalonia.Controls/Primitives/AccessText.cs b/src/Avalonia.Controls/Primitives/AccessText.cs index ed3412bb45..c5dbc3000f 100644 --- a/src/Avalonia.Controls/Primitives/AccessText.cs +++ b/src/Avalonia.Controls/Primitives/AccessText.cs @@ -60,7 +60,7 @@ namespace Avalonia.Controls.Primitives /// Renders the to a drawing context. /// /// The drawing context. - protected internal override void RenderCore(DrawingContext context) + private protected override void RenderCore(DrawingContext context) { base.RenderCore(context); int underscore = Text?.IndexOf('_') ?? -1; diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index 7ed055f2e5..99fd4cd1dc 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -51,7 +51,7 @@ namespace Avalonia.Controls.Primitives } /// - protected internal override Interactive? InteractiveParent => Parent as Interactive; + internal override Interactive? InteractiveParent => Parent as Interactive; /// public void Dispose() => Hide(); diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 80b7841fc7..7ac219aa83 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -727,7 +727,7 @@ namespace Avalonia.Controls.Primitives 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 // set focus to PlacementTarget if this is the case @@ -744,7 +744,7 @@ namespace Avalonia.Controls.Primitives if (e is object) { - FocusManager.Instance?.Focus(e); + e.Focus(); } } else @@ -752,7 +752,7 @@ namespace Avalonia.Controls.Primitives var anc = this.FindLogicalAncestorOfType(); if (anc != null) { - FocusManager.Instance?.Focus(anc); + anc.Focus(); } } } diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index a2034a2dbb..aef97bce36 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -72,7 +72,7 @@ namespace Avalonia.Controls.Primitives /// /// Popup events are passed to their parent window. This facilitates this. /// - protected internal override Interactive? InteractiveParent => (Interactive?)Parent; + internal override Interactive? InteractiveParent => (Interactive?)Parent; /// /// Gets the control that is hosting the popup root. diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index 37aa1ebffd..86bea8daa5 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -200,7 +200,7 @@ namespace Avalonia.Controls.Primitives /// /// This method is automatically called when the control is attached to a visual tree. /// - protected internal virtual void AttachToScrollViewer() + internal void AttachToScrollViewer() { var owner = this.FindAncestorOfType(); diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 44d68fcf30..bef02567bc 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -318,7 +318,7 @@ namespace Avalonia.Controls.Primitives } /// - protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) + internal sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { var count = VisualChildren.Count; diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 35676474dd..bc47462b56 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -104,7 +104,7 @@ namespace Avalonia.Controls.Primitives } /// - protected override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) + internal override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { foreach (var l in _layers) ((ILogical)l).NotifyResourcesChanged(e); diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index adef052db8..c1edf23c89 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -555,7 +555,7 @@ namespace Avalonia.Controls } // Workaround to seal Render method, we need to make so because AccessText was overriding Render method which is sealed now. - internal protected virtual void RenderCore(DrawingContext context) + private protected virtual void RenderCore(DrawingContext context) { var background = Background; diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 85a35a3489..67847f621b 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -452,6 +452,9 @@ namespace Avalonia.Controls /// public IClipboard? Clipboard => PlatformImpl?.TryGetFeature(); + /// + public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService(); + /// Point IRenderRoot.PointToClient(PixelPoint p) { @@ -725,7 +728,7 @@ namespace Avalonia.Controls void PlatformImpl_LostFocus() { - var focused = (Visual?)FocusManager.Instance?.Current; + var focused = (Visual?)FocusManager?.GetFocusedElement(); if (focused == null) return; while (focused.VisualParent != null) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index ef10dcdb22..a499829d4a 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -560,8 +560,7 @@ namespace Avalonia.Controls if (next != null) { - FocusManager.Instance?.Focus(next, NavigationMethod.Directional); - e.Handled = true; + e.Handled = next.Focus(NavigationMethod.Directional); } } else diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index cf12c12447..a48706cb19 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -238,7 +238,7 @@ namespace Avalonia.Controls } else { - FocusManager.Instance?.Focus(treeViewItem, NavigationMethod.Directional); + treeViewItem.Focus(NavigationMethod.Directional); } return true; diff --git a/src/Avalonia.Controls/Viewbox.cs b/src/Avalonia.Controls/Viewbox.cs index 1518fb49e3..b21a04c1d4 100644 --- a/src/Avalonia.Controls/Viewbox.cs +++ b/src/Avalonia.Controls/Viewbox.cs @@ -82,7 +82,7 @@ namespace Avalonia.Controls /// Gets or sets the transform applied to the container visual that /// hosts the child of the Viewbox /// - protected internal ITransform? InternalTransform + internal ITransform? InternalTransform { get => _containerVisual.RenderTransform; set => _containerVisual.RenderTransform = value; diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index 40c67debe0..b234686f5e 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -356,7 +356,7 @@ namespace Avalonia.Controls { 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; if (GetRealizedElement(index) is Control element) @@ -682,9 +682,12 @@ namespace Avalonia.Controls _scrollViewer?.UnregisterAnchorCandidate(element); 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; } @@ -707,9 +710,8 @@ namespace Avalonia.Controls Debug.Assert(ItemContainerGenerator is not null); var recycleKey = element.GetValue(RecycleKeyProperty); - Debug.Assert(recycleKey is not null); - - if (recycleKey == s_itemIsItsOwnContainer) + + if (recycleKey is null || recycleKey == s_itemIsItsOwnContainer) { RemoveInternalChild(element); } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index ac47e744e0..b19ad49820 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -234,7 +234,7 @@ namespace Avalonia.Controls if (this is IFocusScope scope) { - FocusManager.Instance?.RemoveFocusScope(scope); + ((FocusManager?)FocusManager)?.RemoveFocusScope(scope); } base.HandleClosed(); @@ -326,7 +326,7 @@ namespace Avalonia.Controls if (scope != null) { - FocusManager.Instance?.SetFocusScope(scope); + ((FocusManager?)FocusManager)?.SetFocusScope(scope); } IsActive = true; diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index cb896fd633..ff49d0dd87 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -86,7 +86,7 @@ namespace Avalonia.Diagnostics private static IDisposable Open(IDevToolsTopLevelGroup topLevelGroup, DevToolsOptions options, Window? owner, Application? app) { - var focussedControl = KeyboardDevice.Instance?.FocusedElement as Control; + var focusedControl = owner?.FocusManager?.GetFocusedElement() as Control; AvaloniaObject root = topLevelGroup switch { ClassicDesktopStyleApplicationLifetimeTopLevelGroup gr => new Controls.Application(gr, app ?? Application.Current!), @@ -98,7 +98,7 @@ namespace Avalonia.Diagnostics if (s_open.TryGetValue(topLevelGroup, out var mainWindow)) { mainWindow.Activate(); - mainWindow.SelectedControl(focussedControl); + mainWindow.SelectedControl(focusedControl); return Disposable.Empty; } if (topLevelGroup.Items.Count == 1 && topLevelGroup.Items is not INotifyCollectionChanged) @@ -110,7 +110,7 @@ namespace Avalonia.Diagnostics if (group.Key.Items.Contains(singleTopLevel)) { group.Value.Activate(); - group.Value.SelectedControl(focussedControl); + group.Value.SelectedControl(focusedControl); return Disposable.Empty; } } @@ -124,7 +124,7 @@ namespace Avalonia.Diagnostics Tag = topLevelGroup }; window.SetOptions(options); - window.SelectedControl(focussedControl); + window.SelectedControl(focusedControl); window.Closed += DevToolsClosed; s_open.Add(topLevelGroup, window); if (options.ShowAsChildWindow && owner is not null) diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml index 1d9815713c..6218fe94ca 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml @@ -33,11 +33,12 @@ VerticalSnapPointsType="{TemplateBinding VerticalSnapPointsType}" HorizontalSnapPointsAlignment="{TemplateBinding HorizontalSnapPointsAlignment}" VerticalSnapPointsAlignment="{TemplateBinding VerticalSnapPointsAlignment}" - Padding="{TemplateBinding Padding}"> + Padding="{TemplateBinding Padding}" + ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}"> + IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/> + Background="{TemplateBinding Background}" + ScrollViewer.IsScrollInertiaEnabled="{TemplateBinding IsScrollInertiaEnabled}"> + IsScrollInertiaEnabled="{Binding (ScrollViewer.IsScrollInertiaEnabled), ElementName=PART_ContentPresenter}"/> e.ClrAssemblyName is { Length: > 0 }); + foreach (var entry in resolvable) { var asm = Assembly.Load(new AssemblyName(entry.ClrAssemblyName)); var resolved = asm.GetType(entry.ClrNamespace + "." + name); @@ -164,7 +165,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime throw new ArgumentException( $"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" }; } } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 18cb87dfd1..f74ba179c1 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -7,7 +7,6 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.Utilities; using Avalonia.Utilities; -using Avalonia.Media.Imaging; using SkiaSharp; using ISceneBrush = Avalonia.Media.ISceneBrush; @@ -26,7 +25,7 @@ namespace Avalonia.Skia private readonly Stack _opacityStack = new(); private readonly Matrix? _postTransform; private double _currentOpacity = 1.0f; - private readonly bool _canTextUseLcdRendering; + private readonly bool _disableSubpixelTextRendering; private Matrix _currentTransform; private bool _disposed; private GRContext? _grContext; @@ -59,11 +58,11 @@ namespace Avalonia.Skia /// Dpi of drawings. /// public Vector Dpi; - + /// - /// Render text without Lcd rendering. + /// Render text without subpixel antialiasing. /// - public bool DisableTextLcdRendering; + public bool DisableSubpixelTextRendering; /// /// GPU-accelerated context (optional) @@ -135,7 +134,7 @@ namespace Avalonia.Skia _dpi = createInfo.Dpi; _disposables = disposables; - _canTextUseLcdRendering = !createInfo.DisableTextLcdRendering; + _disableSubpixelTextRendering = createInfo.DisableSubpixelTextRendering; _grContext = createInfo.GrContext; _gpu = createInfo.Gpu; if (_grContext != null) @@ -519,7 +518,23 @@ namespace Avalonia.Skia { 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, (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint); @@ -969,6 +984,7 @@ namespace Avalonia.Skia using (var ctx = intermediate.CreateDrawingContext()) { + ctx.RenderOptions = RenderOptions; ctx.Clear(Colors.Transparent); 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 ctx = pictureTarget.CreateDrawingContext(calc.IntermediateSize)) { + ctx.RenderOptions = RenderOptions; ctx.PushClip(calc.IntermediateClip); content.Render(ctx, transform); ctx.PopClip(); @@ -1283,7 +1300,7 @@ namespace Avalonia.Skia Height = pixelSize.Height, Dpi = _dpi, Format = format, - DisableTextLcdRendering = !_canTextUseLcdRendering, + DisableTextLcdRendering = isLayer ? _disableSubpixelTextRendering : true, GrContext = _grContext, Gpu = _gpu, Session = _session, diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs index a22b67e09e..64afaa6817 100644 --- a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -3,7 +3,6 @@ using System.Diagnostics.CodeAnalysis; using Avalonia.Reactive; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; -using Avalonia.Rendering; using SkiaSharp; namespace Avalonia.Skia @@ -54,8 +53,7 @@ namespace Avalonia.Skia var createInfo = new DrawingContextImpl.CreateInfo { Surface = _framebufferSurface, - Dpi = framebuffer.Dpi, - DisableTextLcdRendering = true + Dpi = framebuffer.Dpi }; return new DrawingContextImpl(createInfo, _preFramebufferCopyHandler, canvas, framebuffer); diff --git a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs index ce292eb826..6db6083ba7 100644 --- a/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/Gpu/SkiaGpuRenderTarget.cs @@ -30,7 +30,6 @@ namespace Avalonia.Skia GrContext = session.GrContext, Surface = session.SkSurface, Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor, - DisableTextLcdRendering = true, Gpu = _skiaGpu, CurrentSession = session }; diff --git a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs index b774ddf411..6a726dc9dc 100644 --- a/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/DrawingContextHelper.cs @@ -20,7 +20,7 @@ namespace Avalonia.Skia.Helpers { Canvas = canvas, Dpi = dpi, - DisableTextLcdRendering = true, + DisableSubpixelTextRendering = true, }; return new DrawingContextImpl(createInfo); diff --git a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs b/src/Skia/Avalonia.Skia/PictureRenderTarget.cs index 280b7c27cd..02cc1c0676 100644 --- a/src/Skia/Avalonia.Skia/PictureRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/PictureRenderTarget.cs @@ -39,7 +39,7 @@ internal class PictureRenderTarget : IDisposable { Canvas = canvas, Dpi = _dpi, - DisableTextLcdRendering = true, + DisableSubpixelTextRendering = true, GrContext = _grContext, Gpu = _gpu, }; @@ -52,4 +52,4 @@ internal class PictureRenderTarget : IDisposable } public void Dispose() => _picture?.Dispose(); -} \ No newline at end of file +} diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 92210c30e2..c695e8ba41 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -106,7 +106,7 @@ namespace Avalonia.Skia { Surface = _surface.Surface, Dpi = Dpi, - DisableTextLcdRendering = _disableLcdRendering, + DisableSubpixelTextRendering = _disableLcdRendering, GrContext = _grContext, Gpu = _gpu, }; diff --git a/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs b/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs index a8060d3fbf..8d73bde919 100644 --- a/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs +++ b/src/Windows/Avalonia.Win32.Interop/WinForms/WinFormsAvaloniaControlHost.cs @@ -22,7 +22,7 @@ namespace Avalonia.Win32.Embedding UnmanagedMethods.SetParent(WindowHandle, Handle); _root.Prepare(); if (_root.IsFocused) - FocusManager.Instance.Focus(null); + _root.FocusManager.ClearFocus(); _root.GotFocus += RootGotFocus; FixPosition(); diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 24fd7e3933..8025779c90 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -141,13 +141,13 @@ namespace Avalonia.Win32.Interop.Wpf { var state = Keyboard.Modifiers; var rv = default(RawInputModifiers); - if (state.HasAllFlags(ModifierKeys.Windows)) + if (state.HasFlag(ModifierKeys.Windows)) rv |= RawInputModifiers.Meta; - if (state.HasAllFlags(ModifierKeys.Alt)) + if (state.HasFlag(ModifierKeys.Alt)) rv |= RawInputModifiers.Alt; - if (state.HasAllFlags(ModifierKeys.Control)) + if (state.HasFlag(ModifierKeys.Control)) rv |= RawInputModifiers.Control; - if (state.HasAllFlags(ModifierKeys.Shift)) + if (state.HasFlag(ModifierKeys.Shift)) rv |= RawInputModifiers.Shift; if (e != null) { diff --git a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs index 7e1e22579b..4f9b6c54d3 100644 --- a/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs +++ b/src/Windows/Avalonia.Win32/Input/WindowsKeyboardDevice.cs @@ -5,7 +5,7 @@ using Avalonia.Win32.Interop; namespace Avalonia.Win32.Input { - class WindowsKeyboardDevice : KeyboardDevice + internal class WindowsKeyboardDevice : KeyboardDevice { private readonly byte[] _keyStates = new byte[256]; diff --git a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs index ac1547d09f..aa2e47de2d 100644 --- a/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs +++ b/tests/Avalonia.Base.UnitTests/Input/InputElement_Focus.cs @@ -23,7 +23,7 @@ namespace Avalonia.Base.UnitTests.Input 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} }; - Assert.Null(FocusManager.Instance.Current); + Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); Assert.False(target.IsFocused); 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(); Assert.False(target.IsFocused); 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(); - Assert.Same(first, FocusManager.Instance.Current); + Assert.Same(first, root.FocusManager.GetFocusedElement()); 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 } }; - Assert.Null(FocusManager.Instance.Current); + Assert.Null(root.FocusManager.GetFocusedElement()); target.Focus(); Assert.False(target.IsFocused); 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(); Assert.False(target.IsFocused); 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.IsVisible = false; - Assert.Null(FocusManager.Instance.Current); + Assert.Null(root.FocusManager.GetFocusedElement()); } } @@ -224,7 +224,7 @@ namespace Avalonia.Base.UnitTests.Input target.Focus(); 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.IsEnabled = false; - Assert.Null(FocusManager.Instance.Current); + Assert.Null(root.FocusManager.GetFocusedElement()); } } @@ -266,7 +266,7 @@ namespace Avalonia.Base.UnitTests.Input target.Focus(); container.IsEnabled = false; - Assert.Null(FocusManager.Instance.Current); + Assert.Null(root.FocusManager.GetFocusedElement()); } } @@ -285,7 +285,7 @@ namespace Avalonia.Base.UnitTests.Input target.Focus(); root.Child = null; - Assert.Null(FocusManager.Instance.Current); + Assert.Null(root.FocusManager.GetFocusedElement()); } } @@ -312,13 +312,13 @@ namespace Avalonia.Base.UnitTests.Input target2.ApplyTemplate(); - FocusManager.Instance?.Focus(target1); + target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus")); Assert.False(target2.IsFocused); Assert.False(target2.Classes.Contains(":focus")); - FocusManager.Instance?.Focus(target2, NavigationMethod.Tab); + target2.Focus(NavigationMethod.Tab); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus")); Assert.True(target2.IsFocused); @@ -348,19 +348,19 @@ namespace Avalonia.Base.UnitTests.Input target1.ApplyTemplate(); target2.ApplyTemplate(); - FocusManager.Instance?.Focus(target1); + target1.Focus(); Assert.True(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-visible")); Assert.False(target2.IsFocused); Assert.False(target2.Classes.Contains(":focus-visible")); - FocusManager.Instance?.Focus(target2, NavigationMethod.Tab); + target2.Focus(NavigationMethod.Tab); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-visible")); Assert.True(target2.IsFocused); Assert.True(target2.Classes.Contains(":focus-visible")); - FocusManager.Instance?.Focus(target1, NavigationMethod.Directional); + target1.Focus(NavigationMethod.Directional); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-visible")); Assert.False(target2.IsFocused); @@ -390,7 +390,7 @@ namespace Avalonia.Base.UnitTests.Input target1.ApplyTemplate(); target2.ApplyTemplate(); - FocusManager.Instance?.Focus(target1); + target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); @@ -425,7 +425,7 @@ namespace Avalonia.Base.UnitTests.Input target1.ApplyTemplate(); target2.ApplyTemplate(); - FocusManager.Instance?.Focus(target1); + target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); @@ -436,7 +436,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(root.Classes.Contains(":focus-within")); Assert.True(root.IsKeyboardFocusWithin); - FocusManager.Instance?.Focus(target2); + target2.Focus(); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); @@ -478,7 +478,7 @@ namespace Avalonia.Base.UnitTests.Input target1.ApplyTemplate(); target2.ApplyTemplate(); - FocusManager.Instance?.Focus(target1); + target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); @@ -534,7 +534,7 @@ namespace Avalonia.Base.UnitTests.Input target1.ApplyTemplate(); target2.ApplyTemplate(); - FocusManager.Instance?.Focus(target1); + target1.Focus(); Assert.True(target1.IsFocused); Assert.True(target1.Classes.Contains(":focus-within")); Assert.True(target1.IsKeyboardFocusWithin); @@ -545,7 +545,7 @@ namespace Avalonia.Base.UnitTests.Input Assert.Equal(KeyboardDevice.Instance.FocusedElement, target1); - FocusManager.Instance?.Focus(target2); + target2.Focus(); Assert.False(target1.IsFocused); Assert.False(target1.Classes.Contains(":focus-within")); @@ -578,9 +578,9 @@ namespace Avalonia.Base.UnitTests.Input }; target.Focus(); - FocusManager.Instance.Focus(null); + root.FocusManager.ClearFocus(); - Assert.Null(FocusManager.Instance.Current); + Assert.Null(root.FocusManager.GetFocusedElement()); } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs index 34a9947d28..0b3d1a275b 100644 --- a/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs +++ b/tests/Avalonia.Base.UnitTests/Input/KeyboardNavigationTests_Tab.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; +using Avalonia.UnitTests; using Xunit; namespace Avalonia.Base.UnitTests.Input @@ -1253,5 +1254,24 @@ namespace Avalonia.Base.UnitTests.Input Assert.Same(expected, result); } + + [Fact] + public void Focuses_First_Child_From_No_Focus() + { + using var app = UnitTestApplication.Start(TestServices.RealFocus); + var button = new Button(); + var root = new TestRoot(button); + var target = new KeyboardNavigationHandler(); + + target.SetOwner(root); + + root.RaiseEvent(new KeyEventArgs + { + RoutedEvent = InputElement.KeyDownEvent, + Key = Key.Tab, + }); + + Assert.True(button.IsFocused); + } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs index 629188800a..ae6b601896 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs @@ -20,7 +20,9 @@ namespace Avalonia.Base.UnitTests.Input [Fact] 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 device = CreatePointerDeviceMock().Object; diff --git a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs index 7767de11c7..db12de9db9 100644 --- a/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs +++ b/tests/Avalonia.Controls.UnitTests/FlyoutTests.cs @@ -288,10 +288,10 @@ namespace Avalonia.Controls.UnitTests window.Show(); button.Focus(); - Assert.True(FocusManager.Instance?.Current == button); + Assert.True(window.FocusManager.GetFocusedElement() == button); button.Flyout.ShowAt(button); 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.Show(); - FocusManager.Instance?.Focus(button); - Assert.True(FocusManager.Instance?.Current == button); + button.Focus(); + Assert.True(window.FocusManager.GetFocusedElement() == button); button.Flyout.ShowAt(button); - Assert.True(FocusManager.Instance?.Current == button); + Assert.True(window.FocusManager.GetFocusedElement() == button); } } diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 86249c66ff..58e4ec1b75 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -576,8 +576,9 @@ namespace Avalonia.Controls.UnitTests }); var panel = Assert.IsAssignableFrom(target.ItemsPanelRoot); + var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager; - Assert.Equal(panel.Children[1], FocusManager.Instance!.Current); + Assert.Equal(panel.Children[1], focusManager?.GetFocusedElement()); } [Fact] @@ -601,8 +602,9 @@ namespace Avalonia.Controls.UnitTests }); var panel = Assert.IsAssignableFrom(target.ItemsPanelRoot); + var focusManager = ((IInputRoot)target.VisualRoot!).FocusManager; - Assert.Equal(panel.Children[2], FocusManager.Instance!.Current); + Assert.Equal(panel.Children[2], focusManager?.GetFocusedElement()); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index 3b534a0047..6d0b543ede 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -1151,7 +1151,7 @@ namespace Avalonia.Controls.UnitTests RaiseKeyEvent(button, Key.Tab); var item = target.ContainerFromIndex(0); - Assert.Same(item, FocusManager.Instance.Current); + Assert.Same(item, root.FocusManager.GetFocusedElement()); } [Fact] @@ -1198,17 +1198,17 @@ namespace Avalonia.Controls.UnitTests RaiseKeyEvent(button, Key.Tab); var item = target.ContainerFromIndex(1); - Assert.Same(item, FocusManager.Instance.Current); + Assert.Same(item, root.FocusManager.GetFocusedElement()); RaiseKeyEvent(item, Key.Tab); - Assert.Same(button, FocusManager.Instance.Current); + Assert.Same(button, root.FocusManager.GetFocusedElement()); target.Selection.AnchorIndex = 2; RaiseKeyEvent(button, Key.Tab); 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) diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index e5c96dcab6..eaf95b0c8c 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -267,7 +267,7 @@ namespace Avalonia.Controls.UnitTests.Platform target.KeyDown(item.Object, e); parentItem.Verify(x => x.Close()); - parentItem.Verify(x => x.Focus()); + parentItem.Verify(x => x.Focus(It.IsAny(), It.IsAny())); Assert.True(e.Handled); } @@ -351,7 +351,7 @@ namespace Avalonia.Controls.UnitTests.Platform target.KeyDown(item.Object, e); parentItem.Verify(x => x.Close()); - parentItem.Verify(x => x.Focus()); + parentItem.Verify(x => x.Focus(It.IsAny(), It.IsAny())); Assert.True(e.Handled); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 765f2d1c19..51399d1202 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -642,10 +642,11 @@ namespace Avalonia.Controls.UnitTests.Primitives tb.Focus(); - Assert.True(FocusManager.Instance?.Current == tb); + var focusManager = TopLevel.GetTopLevel(tb)!.FocusManager; + tb = Assert.IsType(focusManager.GetFocusedElement()); //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); @@ -684,7 +685,8 @@ namespace Avalonia.Controls.UnitTests.Primitives p.Close(); - var focus = FocusManager.Instance?.Current; + var focusManager = window.FocusManager; + var focus = focusManager.GetFocusedElement(); Assert.True(focus == window); } } @@ -723,7 +725,8 @@ namespace Avalonia.Controls.UnitTests.Primitives windowTB.Focus(); - var focus = FocusManager.Instance?.Current; + var focusManager = window.FocusManager; + var focus = focusManager.GetFocusedElement(); Assert.True(focus == windowTB); diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 0d3eb80ae7..398ac3ff43 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -461,7 +461,7 @@ namespace Avalonia.Controls.UnitTests RaiseKeyEvent(button, Key.Tab); var item = target.ContainerFromIndex(0); - Assert.Same(item, FocusManager.Instance.Current); + Assert.Same(item, root.FocusManager.GetFocusedElement()); } [Fact] @@ -513,17 +513,17 @@ namespace Avalonia.Controls.UnitTests RaiseKeyEvent(button, Key.Tab); var item = target.ContainerFromIndex(1); - Assert.Same(item, FocusManager.Instance.Current); + Assert.Same(item, root.FocusManager.GetFocusedElement()); RaiseKeyEvent(item, Key.Tab); - Assert.Same(button, FocusManager.Instance.Current); + Assert.Same(button, root.FocusManager.GetFocusedElement()); target.Selection.AnchorIndex = 2; RaiseKeyEvent(button, Key.Tab); item = target.ContainerFromIndex(2); - Assert.Same(item, FocusManager.Instance.Current); + Assert.Same(item, root.FocusManager.GetFocusedElement()); } private static IControlTemplate TabControlTemplate() diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 51300f343a..604ff1c715 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -969,7 +969,6 @@ namespace Avalonia.Controls.UnitTests public void Keyboard_Navigation_Should_Move_To_Last_Selected_Node() { using var app = Start(); - var focus = FocusManager.Instance!; var navigation = AvaloniaLocator.Current.GetRequiredService(); var data = CreateTestTreeData(); @@ -984,6 +983,7 @@ namespace Avalonia.Controls.UnitTests { Children = { target, button }, }); + var focus = root.FocusManager; root.LayoutManager.ExecuteInitialLayoutPass(); ExpandAll(target); @@ -994,20 +994,19 @@ namespace Avalonia.Controls.UnitTests target.SelectedItem = item; node.Focus(); - Assert.Same(node, focus.Current); + Assert.Same(node, focus.GetFocusedElement()); - navigation.Move(focus.Current!, NavigationDirection.Next); - Assert.Same(button, focus.Current); + navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next); + Assert.Same(button, focus.GetFocusedElement()); - navigation.Move(focus.Current!, NavigationDirection.Next); - Assert.Same(node, focus.Current); + navigation.Move(focus.GetFocusedElement()!, NavigationDirection.Next); + Assert.Same(node, focus.GetFocusedElement()); } [Fact] public void Keyboard_Navigation_Should_Not_Crash_If_Selected_Item_Is_not_In_Tree() { using var app = Start(); - var focus = FocusManager.Instance!; var data = CreateTestTreeData(); var selectedNode = new Node { Value = "Out of Tree Selected Item" }; @@ -1025,6 +1024,7 @@ namespace Avalonia.Controls.UnitTests { Children = { target, button }, }); + var focus = root.FocusManager; root.LayoutManager.ExecuteInitialLayoutPass(); ExpandAll(target); @@ -1035,7 +1035,7 @@ namespace Avalonia.Controls.UnitTests target.SelectedItem = selectedNode; node.Focus(); - Assert.Same(node, focus.Current); + Assert.Same(node, focus.GetFocusedElement()); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs index aa03a77d70..c5a6ecc376 100644 --- a/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/VirtualizingStackPanelTests.cs @@ -646,7 +646,7 @@ namespace Avalonia.Controls.UnitTests { // Issue #11272 using var app = App(); - var (_, _, itemsControl) = CreateUnrootedTarget(); + var (_, _, itemsControl) = CreateUnrootedTarget(); var container = new Decorator { Margin = new Thickness(100) }; var root = new TestRoot(true, container); @@ -657,6 +657,78 @@ namespace Avalonia.Controls.UnitTests root.LayoutManager.ExecuteLayoutPass(); } + [Fact] + public void Supports_Null_Recycle_Key_When_Scrolling() + { + using var app = App(); + var (_, scroll, itemsControl) = CreateUnrootedTarget(); + 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(); + 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(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 GetRealizedIndexes(VirtualizingStackPanel target, ItemsControl itemsControl) { return target.GetRealizedElements() @@ -704,7 +776,7 @@ namespace Avalonia.Controls.UnitTests Optional itemTemplate = default, IEnumerable