using System.ComponentModel; using Avalonia.Controls; using Avalonia.Threading; using Avalonia.VisualTree; namespace DodoSSH.Client.App.Views; /// /// Hands the keyboard to a confirmation's harmless answer when the question appears. /// /// /// /// For the questions drawn in place on a page — the deletion and the sign-out confirmations — which used to /// appear with the keyboard still on the button that asked them, or nowhere. Tab then walked the page behind /// the question, Enter did nothing useful, and somebody working from the keyboard had to reach for the mouse /// to say no. /// /// /// ◆ The harmless answer, never the one the question is about. Enter on a focused button presses it, /// so the button that takes the keyboard is the one an accidental Enter is allowed to press: CANCEL. Putting it /// on DELETE or SIGN OUT AND DELETE would make two keystrokes — the one that asked and the one after — a way to /// destroy something without reading the sentence in between. /// /// /// Each card follows its own question rather than the window following all of them, because the same card is /// drawn in several places and only the one somebody can see should take the keyboard — which the posted /// check on IsEffectivelyVisible decides, after the binding that reveals it has run. Subscribed only /// while the card is in the tree: settings pages are rebuilt as they are visited, and a card that subscribed /// for good would leave one handler on the long-lived view model per visit. /// /// internal static class SafeAnswerFocus { internal static void Follow(Control card, string question, Func safeAnswer) { INotifyPropertyChanged? watched = null; void OnChanged(object? sender, PropertyChangedEventArgs e) { if (!string.Equals(e.PropertyName, question, StringComparison.Ordinal)) { return; } Dispatcher.UIThread.Post( () => { if (safeAnswer() is { IsEffectivelyVisible: true } answer) { answer.Focus(); } }, DispatcherPriority.Loaded); } void Watch(INotifyPropertyChanged? next) { if (watched is not null) { watched.PropertyChanged -= OnChanged; } watched = next; if (watched is not null) { watched.PropertyChanged += OnChanged; } } card.AttachedToVisualTree += (_, _) => Watch(card.DataContext as INotifyPropertyChanged); card.DetachedFromVisualTree += (_, _) => Watch(null); card.DataContextChanged += (_, _) => { if (card.IsAttachedToVisualTree()) { Watch(card.DataContext as INotifyPropertyChanged); } }; } }