using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using DodoSSH.Client.Shell.ViewModels;
namespace DodoSSH.Client.App.Views;
/// The tab strip, above every screen.
internal sealed partial class TerminalTabs : UserControl
{
public TerminalTabs() => InitializeComponent();
///
/// Closes a tab on a middle click.
///
///
///
/// Wired on the tab's own template root, which is the whole answer to "and not on the strip itself".
/// A middle press on the background, on the sentence, or on the button that opens a connection reaches
/// no handler at all, because there is none there to reach. Nothing has to test what was clicked.
///
///
/// PointerUpdateKind, not IsMiddleButtonPressed. The latter reports button
/// state: it is equally true for a left press made while the middle button happens to be held,
/// and for every press during a middle drag. The question here is which button caused this press, and
/// that is the one thing only PointerUpdateKind answers.
///
///
/// On press rather than on release, which is what every browser and every terminal does. Matching a
/// release to its press would need capture tracking, to buy the ability to change your mind about a
/// middle click — a gesture nobody makes by accident and nobody aborts.
///
///
private void OnTabPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (sender is not Visual { DataContext: TerminalTabViewModel tab }
|| DataContext is not MainWindowViewModel shell)
{
return;
}
if (e.GetCurrentPoint((Visual)sender).Properties.PointerUpdateKind
is not PointerUpdateKind.MiddleButtonPressed)
{
return;
}
// Handled, so the strip's ScrollViewer does not also take this as the start of a pan.
e.Handled = true;
// Fire-and-forget, as the host sidebar's double-tap connect is: CloseTabCommand is asynchronous —
// it waits for the workspace to tear the session down — and an event handler has nowhere to await
// it. Its failures are the workspace's to report, not this strip's.
shell.CloseTabCommand.Execute(tab);
}
///
/// Opens the vault menu, on the Vaults tab.
///
///
///
/// The tab is selected before the menu opens, and that order is the whole reason this is a handler
/// rather than Button.Flyout. Selecting it puts the shell on a page, which collapses the
/// renderer — so the popup never has to drop over the WebView's native child window, and the question
/// this strip's comment refuses to answer without a screenshot does not come up. See the comment on the
/// caret in the markup, and docs/platform-flags.md for what treating such a question as settled
/// has already cost this project.
///
///
/// It is also what a user expects. The caret belongs to the Vaults tab, so pressing it arriving at
/// Vaults is the same gesture as pressing the tab, with a menu on the end.
///
///
private void OnVaultMenuPressed(object? sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel shell || sender is not Control caret)
{
return;
}
shell.ShowVaultsCommand.Execute(null);
FlyoutBase.ShowAttachedFlyout(caret);
}
/// Leaves for the teams screen with the new-vault form open, shutting the menu behind it.
///
/// The menu is closed first, because the command navigates and a flyout left open would be hanging over
/// a screen it has nothing to do with. A Flyout does not close when something inside it is
/// pressed — which is what the switches above it want, and not what this wants.
///
private void OnNewVaultPressed(object? sender, RoutedEventArgs e)
{
if (DataContext is not MainWindowViewModel shell)
{
return;
}
if (this.FindControl