Public Access
Desktop: give the host-key, delete and sign-out questions the keyboard on their harmless answer, and let Escape say no
This commit is contained in:
1 parent
372a7cbdc7
commit
6c09bdecb6
11 files changed
+498
-92
No files matched your search
@@ -0,0 +1,81 @@
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user