Public Access
82 lines
3.0 KiB
C#
82 lines
3.0 KiB
C#
using System.ComponentModel;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Threading;
|
|
using Avalonia.VisualTree;
|
|
|
|
namespace DodoSSH.Client.App.Views;
|
|
|
|
/// <summary>
|
|
/// Hands the keyboard to a confirmation's harmless answer when the question appears.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// 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.
|
|
/// </para>
|
|
/// <para>
|
|
/// ◆ <b>The harmless answer, never the one the question is about.</b> 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.
|
|
/// </para>
|
|
/// <para>
|
|
/// 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 <c>IsEffectivelyVisible</c> 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.
|
|
/// </para>
|
|
/// </remarks>
|
|
internal static class SafeAnswerFocus
|
|
{
|
|
internal static void Follow(Control card, string question, Func<Control> 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);
|
|
}
|
|
};
|
|
}
|
|
}
|