using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.VisualTree;
using LANCommander.Launcher.Input;
namespace LANCommander.Launcher.Controls;
///
/// Attached behavior that automatically focuses the first focusable descendant
/// when a control is attached to the visual tree.
/// Usage: <UserControl controls:AutoFocus.IsEnabled="True" />
///
public class AutoFocus : AvaloniaObject
{
public static readonly AttachedProperty IsEnabledProperty =
AvaloniaProperty.RegisterAttached("IsEnabled");
static AutoFocus()
{
IsEnabledProperty.Changed.AddClassHandler(OnIsEnabledChanged);
}
public static bool GetIsEnabled(Control control) => control.GetValue(IsEnabledProperty);
public static void SetIsEnabled(Control control, bool value) => control.SetValue(IsEnabledProperty, value);
private static void OnIsEnabledChanged(Control control, AvaloniaPropertyChangedEventArgs e)
{
control.AttachedToVisualTree -= OnAttached;
if (e.NewValue is true)
control.AttachedToVisualTree += OnAttached;
}
private static void OnAttached(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is not Control control) return;
// Only auto-focus when a gamepad has been used; otherwise the focus
// ring appears immediately on every view load for mouse/keyboard users.
if (!GamepadService.HasReceivedInput)
return;
Dispatcher.UIThread.Post(() =>
{
if (!GamepadService.HasReceivedInput)
return;
// Don't steal focus if something is already focused within this control
var topLevel = TopLevel.GetTopLevel(control);
var currentFocus = topLevel?.FocusManager?.GetFocusedElement() as Visual;
if (currentFocus != null && IsDescendantOf(currentFocus, control))
return;
var focusable = control.GetVisualDescendants()
.OfType()
.FirstOrDefault(el => el.Focusable && el.IsEffectivelyVisible);
focusable?.Focus(NavigationMethod.Directional);
}, DispatcherPriority.Loaded);
}
private static bool IsDescendantOf(Visual element, Visual ancestor)
{
var current = element;
while (current != null)
{
if (current == ancestor) return true;
current = current.GetVisualParent() as Visual;
}
return false;
}
}