Give the window its v5b chrome and each session surface its own shell

This commit is contained in:
2026-08-08 00:49:59 +02:00
parent 1b76c51fbb
commit 43c939b697
30 changed files with 4433 additions and 1772 deletions
@@ -0,0 +1,102 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
x:Class="DodoSSH.Client.App.Views.SessionTabRow"
x:Name="Root"
x:DataType="vm:MainWindowViewModel">
<!--
── v5b's in-screen tab row, the replacement for TerminalTabs ───────────────────────────────────────────
One pill per open terminal and the button that opens another — the same list <c>TerminalTabs</c> drew
above the whole window — now drawn inside each of the two screens the design gives a tab row: the
terminal surface and the SFTP surface. See <c>MainWindow.axaml</c> for where the strip itself went and
<c>Palette.axaml</c>'s v5b remark for why the row is 38 tall rather than the old strip's 42.
One control rather than two copies of the same markup, because the two rows share everything except two
things: which colour marks the active tab, and what a click on a tab actually does. Both are handed in
from the usage site rather than branched on a mode property here.
◆ THE COLOUR is <c>Classes="sftp"</c> on this control's own usage in <c>MainWindow.axaml</c> — unmarked
is the terminal row and reads <c>TerminalTabAccent</c>; <c>.sftp</c> reads <c>Magenta</c>. Both are
<c>App.axaml</c> selectors keyed off that class on this element, which is why the two rows need no
binding of their own for it: <c>Button.sesstab.active</c> and <c>.sftp Button.sesstab.active</c> are
the whole of it.
◆ THE CLICK is <see cref="TabCommand"/>, a plain <c>ICommand</c> this control exposes rather than reads
off the shell — the terminal row binds it to <c>SelectTabCommand</c> and the SFTP row to
<c>SelectFilesHostCommand</c>, and neither of those is a decision this control has any business making.
Every tab button's own <c>CommandParameter</c> is the tab itself, exactly as the strip's was.
The close box, the middle-click gesture and the "+" are not parameterised: closing a tab ends its shell
regardless of which screen it was clicked from, and "+" always opens the same palette. See
<c>MainWindowViewModel.CloseTabCommand</c> and <c>ToggleSearchCommand</c>.
-->
<Border Height="38">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<StackPanel Orientation="Horizontal" Spacing="6">
<ItemsControl ItemsSource="{Binding Tabs}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Spacing="6" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:TerminalTabViewModel">
<!--
Marked on IsSelected rather than IsShowing, unlike the old strip: IsShowing is "the terminal
surface is showing this tab's pane", which the SFTP row's own active tab is never true of. A
selected tab survives navigating away from the terminal surface — that is the whole point of
a selection outliving a screen — so both rows agree on what "active" means without either
one needing a flag scoped to just one surface. See App.axaml's own remark on Button.sesstab.
-->
<Button Classes="sesstab"
Classes.active="{Binding IsSelected}"
Command="{Binding #Root.TabCommand}"
CommandParameter="{Binding}"
PointerPressed="OnTabPointerPressed"
ToolTip.Tip="{Binding Address}">
<StackPanel Orientation="Horizontal" Spacing="9" VerticalAlignment="Center">
<!--
Two states, as the strip's own dots always were: green while the shell behind this tab is
running, grey while it is connecting and once it has ended. The design's third, amber,
state has no meaning here — nothing in this application checks whether a host is merely
reachable — so it is not drawn; see design-notes/v5b-fidelity-notes.md.
-->
<Ellipse Classes="dot" Width="8" Height="8" Classes.live="{Binding IsLive}"
VerticalAlignment="Center" />
<TextBlock Text="{Binding Label}" VerticalAlignment="Center" />
<Button Classes="flat close inline" Width="16" Height="16" Padding="0"
VerticalAlignment="Center"
Command="{Binding #Root.((vm:MainWindowViewModel)DataContext).CloseTabCommand}"
CommandParameter="{Binding}"
ToolTip.Tip="Closes this terminal and ends its shell. Middle-click the tab does the same.">
<TextBlock Text="&#x2715;" FontSize="10" HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Button>
</StackPanel>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--
Opens the quick-connect palette, on both rows: "the existing new-connection/quick-connect meaning"
the notes ask this button to keep. See TerminalTabs' own remark, carried over unchanged, on why
this is a palette and never a flyout menu over the terminal's own rectangle.
-->
<Button Classes="sesstab plus"
Command="{Binding ToggleSearchCommand}"
ToolTip.Tip="Open a connection · Ctrl+K">
<TextBlock Text="+" FontSize="15" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Button>
</StackPanel>
</ScrollViewer>
</Border>
</UserControl>