using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using DodoSSH.Client.Shell.ViewModels;
namespace DodoSSH.Client.App.Views;
/// The empty terminal's prompt: what to connect to, and its password when one is needed.
///
/// The keys and the caret live here, as the palette's do in and for its reason: a
/// hosts in a bare window under the headless harness, and the window itself cannot
/// be shown there at all.
///
internal sealed partial class ConnectPrompt : UserControl
{
public ConnectPrompt()
{
InitializeComponent();
// Tunnelled, for the reason QuickConnect gives: the prompt owns Enter, Escape and Tab while the caret is
// in it, and a text box on the route must not get to them first.
AddHandler(KeyDownEvent, OnPromptKey, RoutingStrategies.Tunnel);
// The password line appearing, or going, moves the caret with it: Enter on a name that needs a password
// should leave somebody typing the password, and Escape off it should leave them editing the name.
PasswordLine.PropertyChanged += OnLineChanged;
}
/// The first line's box, for the caret and for the tests.
internal TextBox TargetBox => Target;
/// The password line's box, for the caret and for the tests.
internal TextBox PasswordBox => Password;
private MainWindowViewModel? Shell => DataContext as MainWindowViewModel;
///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
// Posted, for the reason QuickConnect.TakeKeyboard gives at length: at the instant this becomes
// visible its boxes are not in the visual tree yet, and focus is refused to anything that is not.
if (change.Property == IsVisibleProperty && change.GetNewValue())
{
Dispatcher.UIThread.Post(TakeKeyboard, DispatcherPriority.Loaded);
}
}
private void OnLineChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == IsVisibleProperty && IsVisible)
{
Dispatcher.UIThread.Post(TakeKeyboard, DispatcherPriority.Loaded);
}
}
/// Puts the caret on whichever line is asking.
///
/// Both halves, for the reason gives: the terminal's WebView can still
/// hold the native window's focus after it has been collapsed, and an Avalonia focus without the native
/// call is a caret that receives nothing.
///
private void TakeKeyboard()
{
if (!IsEffectivelyVisible)
{
return;
}
if (TopLevel.GetTopLevel(this) is Window window)
{
NativeKeyboardFocus.ReturnTo(window);
}
if (PasswordLine.IsVisible)
{
Password.Focus();
}
else
{
Target.Focus();
Target.CaretIndex = Target.Text?.Length ?? 0;
}
}
private void OnPromptKey(object? sender, KeyEventArgs e)
{
if (Shell is not { IsConnectPromptShowing: true } shell || e.KeyModifiers != KeyModifiers.None)
{
return;
}
switch (e.Key)
{
case Key.Enter:
// Fire-and-forget, as the palette's is: the command reports its own refusals on the prompt,
// and a connection's progress and failure in the tab it opens.
_ = shell.SubmitPromptCommand.ExecuteAsync(null);
e.Handled = true;
break;
case Key.Escape:
shell.CancelPromptCommand.Execute(null);
e.Handled = true;
break;
case Key.Tab:
// Only when there is something to complete to; otherwise Tab moves the focus, as it always does.
e.Handled = shell.CompletePrompt();
if (e.Handled)
{
Target.CaretIndex = Target.Text?.Length ?? 0;
}
break;
default:
break;
}
}
}