Public Access
Merge branch 'claude/host-connection-top-bar-25d04e'
This commit is contained in:
@@ -21,6 +21,16 @@
|
||||
|
||||
The order is the design's rather than the rail's. Terminal sits second, beside Hosts, because those two
|
||||
are the pair a session moves between; on the desktop the terminal is not a rail entry at all.
|
||||
|
||||
── a terminal gets the screen ─────────────────────────────────────────────────────────────────────────
|
||||
Three of the four rows below stand down while a shell is showing: the header, the shells strip and the
|
||||
bottom bar itself. All three are bound on IsShowingPages, which is the same question asked once — the
|
||||
surface is either a page or a terminal, and these are the chrome a page has.
|
||||
|
||||
The arithmetic is why. Header 56, strip 46, bar 64, and the terminal's own two rows on top of that: at
|
||||
360dp the shell was framed by about a third of the display, all of it about somewhere the user was not.
|
||||
What takes their place is one 52-pixel bar drawn by the surface itself, carrying back on the left and
|
||||
the sessions and a + across from it. See TerminalScreen.axaml.
|
||||
-->
|
||||
|
||||
<!--
|
||||
@@ -49,45 +59,54 @@
|
||||
Hidden behind MORE, and that is the design's arrangement rather than a saving. v2 gives every screen
|
||||
one header carrying that screen's own name and its own actions — a back arrow, an add, a refresh —
|
||||
so the five hub screens draw their own and this one stands down rather than stacking a second row of
|
||||
chrome above theirs. It stays on the three destinations that are the product's top level, where the
|
||||
chrome above theirs. It stays on the two destinations that are the product's top level, where the
|
||||
vault's name and the sync light are the most useful thing a header could say.
|
||||
|
||||
Wrapped rather than given a second condition, because Avalonia's bindings have no "and": the wrapper
|
||||
collapses it over a terminal, where the surface draws its own bar and the vault's name is not what
|
||||
the user is looking at. That is one of three rows this Grid stands down while a shell is showing —
|
||||
see the strip and the bottom bar below.
|
||||
-->
|
||||
<Border Grid.Row="0" Background="{StaticResource Chrome}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="0,0,0,1" Padding="14,0" Height="56" IsVisible="{Binding !IsMoreSurface}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
<Panel Grid.Row="0" IsVisible="{Binding IsShowingPages}">
|
||||
<Border Background="{StaticResource Chrome}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="0,0,0,1" Padding="14,0" Height="56"
|
||||
IsVisible="{Binding !IsMoreSurface}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
||||
|
||||
<!--
|
||||
Filled rather than outlined since v2. The mark is the one thing on this header that is not a
|
||||
fact about the vault, and the design gives it the accent as a solid tile — which is also what
|
||||
the launcher icon draws, so the two agree.
|
||||
-->
|
||||
<Border Grid.Column="0" Width="26" Height="26" CornerRadius="8"
|
||||
Background="{StaticResource Accent}" VerticalAlignment="Center">
|
||||
<TextBlock Text=">_" Foreground="{StaticResource AccentInk}"
|
||||
FontFamily="{StaticResource MonoFont}" FontSize="10" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
<!--
|
||||
Filled rather than outlined since v2. The mark is the one thing on this header that is not a
|
||||
fact about the vault, and the design gives it the accent as a solid tile — which is also what
|
||||
the launcher icon draws, so the two agree.
|
||||
-->
|
||||
<Border Grid.Column="0" Width="26" Height="26" CornerRadius="8"
|
||||
Background="{StaticResource Accent}" VerticalAlignment="Center">
|
||||
<TextBlock Text=">_" Foreground="{StaticResource AccentInk}"
|
||||
FontFamily="{StaticResource MonoFont}" FontSize="10" FontWeight="Bold"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
|
||||
<TextBlock Grid.Column="1" Classes="heading" Margin="10,0,8,0" FontSize="16"
|
||||
Text="{Binding Vault.VaultName}" TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="1" Classes="heading" Margin="10,0,8,0" FontSize="16"
|
||||
Text="{Binding Vault.VaultName}" TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<!--
|
||||
The sync light, and it is green only when it has earned it — see SyncLabel. The design draws a
|
||||
permanently green "Synced" here, which is the one claim on that mock-up this application will
|
||||
not make.
|
||||
-->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsFullySynced}" Width="6" Height="6"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="label" FontSize="9" Text="{Binding SyncLabel}" />
|
||||
</StackPanel>
|
||||
<!--
|
||||
The sync light, and it is green only when it has earned it — see SyncLabel. The design draws
|
||||
a permanently green "Synced" here, which is the one claim on that mock-up this application
|
||||
will not make.
|
||||
-->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsFullySynced}" Width="6" Height="6"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="label" FontSize="9" Text="{Binding SyncLabel}" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Grid.Column="3" Classes="icon" Margin="4,0,0,0" Command="{Binding LockCommand}"
|
||||
ToolTip.Tip="Lock the keychain">
|
||||
<TextBlock Text="LOCK" Classes="label" FontSize="8.5" Foreground="{StaticResource TextDim}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Button Grid.Column="3" Classes="icon" Margin="4,0,0,0" Command="{Binding LockCommand}"
|
||||
ToolTip.Tip="Lock the keychain">
|
||||
<TextBlock Text="LOCK" Classes="label" FontSize="8.5"
|
||||
Foreground="{StaticResource TextDim}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Panel>
|
||||
|
||||
<!-- The screens. Only one draws; which one is the shell's business. -->
|
||||
<Panel Grid.Row="1">
|
||||
@@ -180,41 +199,57 @@
|
||||
v2 draws the sessions as pills rather than as a labelled row, and drops the word SHELLS: with a
|
||||
rounded chip carrying a live dot and a name, the label was spending nine characters of a 360dp row
|
||||
saying what the row already looks like.
|
||||
|
||||
On every screen except the one it names. The terminal draws these same sessions in its own bar, and
|
||||
two rows of the same pills — one of them 46 pixels of it — is the arrangement this surface exists to
|
||||
stop. Wrapped rather than given a second condition, because the strip's own visibility is about
|
||||
whether there are any tabs and this one is about which surface is up.
|
||||
-->
|
||||
<Border Grid.Row="2" IsVisible="{Binding HasTabs}" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0" Height="46">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding Tabs}" Margin="12,0" VerticalAlignment="Center">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="6" /></ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TerminalTabViewModel">
|
||||
<Button Classes="row" MinHeight="34" Padding="13,0" CornerRadius="9"
|
||||
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||||
BorderThickness="1"
|
||||
Command="{Binding $parent[views:PhoneShell].((vm:MainWindowViewModel)DataContext).SelectTabCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
||||
<!--
|
||||
Green only while there is a shell behind the tab. It used to be lit unconditionally,
|
||||
which was true when a tab could not exist without a session; one can now — connecting
|
||||
opens the tab first — and a dot that was green before anything had answered would be
|
||||
the one thing on this strip claiming something untrue.
|
||||
-->
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" Width="6" Height="6"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" FontSize="11" Text="{Binding Label}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
<Panel Grid.Row="2" IsVisible="{Binding IsShowingPages}">
|
||||
<Border IsVisible="{Binding HasTabs}" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0" Height="46">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding Tabs}" Margin="12,0" VerticalAlignment="Center">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="6" /></ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TerminalTabViewModel">
|
||||
<Button Classes="row" MinHeight="34" Padding="13,0" CornerRadius="9"
|
||||
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||||
BorderThickness="1"
|
||||
Command="{Binding $parent[views:PhoneShell].((vm:MainWindowViewModel)DataContext).SelectTabCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
||||
<!--
|
||||
Green only while there is a shell behind the tab. It used to be lit unconditionally,
|
||||
which was true when a tab could not exist without a session; one can now —
|
||||
connecting opens the tab first — and a dot that was green before anything had
|
||||
answered would be the one thing on this strip claiming something untrue.
|
||||
-->
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" Width="6" Height="6"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" FontSize="11" Text="{Binding Label}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Panel>
|
||||
|
||||
<!-- ============ navigation ============ -->
|
||||
<Border Grid.Row="3" Background="{StaticResource Chrome}" BorderBrush="{StaticResource Border}"
|
||||
<!--
|
||||
Gone while a terminal is showing, which is the whole of that surface's arrangement: the bar's four
|
||||
destinations are replaced by a back arrow and a + that leads to three of them, both in the terminal's
|
||||
own bar. See TerminalScreen.axaml.
|
||||
|
||||
This one is bound directly rather than wrapped — its visibility is a single question and it has no
|
||||
second condition of its own to keep separate.
|
||||
-->
|
||||
<Border Grid.Row="3" IsVisible="{Binding IsShowingPages}"
|
||||
Background="{StaticResource Chrome}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="0,1,0,0" Height="64">
|
||||
<Grid ColumnDefinitions="*,*,*,*">
|
||||
|
||||
@@ -225,8 +260,13 @@
|
||||
<!--
|
||||
The terminal is a surface rather than a page — see ShellSurface — so this one does not go
|
||||
through ShowScreen. Its own command is on the shell.
|
||||
|
||||
The only entry here that never lights, and deliberately no longer tries: this bar is collapsed
|
||||
while the terminal is showing, so IsCurrent could only ever be read as false. Binding it anyway
|
||||
would be a rule about a state this control cannot be in. What marks the terminal as current is
|
||||
the surface filling the screen.
|
||||
-->
|
||||
<views:NavButton Grid.Column="1" Label="Terminal" Glyph="⌗" IsCurrent="{Binding IsTerminalSurface}"
|
||||
<views:NavButton Grid.Column="1" Label="Terminal" Glyph="⌗"
|
||||
Command="{Binding ShowTerminalCommand}" />
|
||||
|
||||
<!--
|
||||
|
||||
@@ -298,6 +298,13 @@ internal sealed partial class PhoneShell : UserControl
|
||||
/// moving between screens at all. Closing an editor is not the same refusal as leaving a host-key
|
||||
/// decision alone — an editor is abandonable by design, and the CANCEL button beside it says so.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>The connect menu is a second such guard, and it matters more than the first.</b> A terminal now
|
||||
/// fills the screen — no header, no bottom bar — so while that menu is up this gesture is the only way
|
||||
/// off it other than the scrim and CANCEL. It is checked before the terminal is dismissed for the
|
||||
/// reason it is drawn over it: back takes the topmost thing, and dismissing the surface underneath a
|
||||
/// menu would take two, neither of them the one being looked at.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private void OnBackRequested(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
@@ -313,6 +320,17 @@ internal sealed partial class PhoneShell : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
// The connect menu, which is raised from the terminal's own bar and is the topmost thing the phone
|
||||
// draws while it is up. Ahead of the editors below because it is nearer, and ahead of leaving the
|
||||
// terminal because a gesture that dismissed the surface underneath a menu would close two things at
|
||||
// once — and the one the user was looking at would not be either of them.
|
||||
if (current.IsConnectSheetOpen)
|
||||
{
|
||||
current.CloseConnectSheetCommand.Execute(null);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (TryCloseAnOpenEditor(current))
|
||||
{
|
||||
e.Handled = true;
|
||||
|
||||
@@ -16,113 +16,119 @@
|
||||
|
||||
The tab strip is horizontal-scrolling rather than wrapping. Wrapping would reflow the terminal every
|
||||
time a tab opened, which is the one thing a terminal must not do while output is arriving.
|
||||
|
||||
── the screen a shell gets ───────────────────────────────────────────────────────────────────────────
|
||||
A connected phone shows one bar and then the terminal. The vault header, the shells strip and the
|
||||
four-entry bottom bar are all collapsed by PhoneShell while this surface is up, and what replaces them
|
||||
is the row below: back, the sessions, and the way to open another one.
|
||||
|
||||
That is a trade, and the thing bought is the only one a terminal really wants. At 360dp the chrome this
|
||||
screen used to sit inside came to 254 pixels of a roughly 780-pixel display — a third of it — and every
|
||||
one of those rows was about somewhere the user was not. What is given up is the bottom bar's one-tap
|
||||
reach to Hosts, Keychain and MORE; back and the + between them lead to all of it, and the system back
|
||||
gesture does the same thing the arrow does.
|
||||
-->
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,*,Auto">
|
||||
<Panel>
|
||||
|
||||
<!-- ============ tabs ============ -->
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
|
||||
<!-- ============ the bar ============ -->
|
||||
<!--
|
||||
v2 draws these as pills rather than as a segmented strip, so the row is transparent and each session
|
||||
carries its own outline. The close cross moved inside the pill with the name, which is what makes it
|
||||
read as one object you can dismiss rather than as two adjacent targets.
|
||||
-->
|
||||
<Border Grid.Row="0" Height="52">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding Tabs}" Margin="12,0" VerticalAlignment="Center">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="6" /></ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TerminalTabViewModel">
|
||||
<!--
|
||||
44 tall, where the session pills on the shell strip are 34. The difference is the close
|
||||
cross: a pill you only select can be chip-sized, and a pill containing the control that ends
|
||||
a shell cannot. This head's rule is 44 and this is the one control on the phone that is both
|
||||
destructive and has no confirmation and no undo — see CloseTabAsync, which ends the session
|
||||
the moment it is pressed.
|
||||
-->
|
||||
<Border Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||||
BorderThickness="1" CornerRadius="11" Height="44">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Classes="row" MinHeight="42" Padding="13,0" CornerRadius="11"
|
||||
Command="{Binding $parent[views:TerminalScreen].((vm:MainWindowViewModel)DataContext).SelectTabCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
||||
<!-- Green only while there is a shell behind it; see the same dot in PhoneShell. -->
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" Width="6" Height="6"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" FontSize="12" FontWeight="SemiBold"
|
||||
Text="{Binding Label}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<!--
|
||||
The close cross is inside the tab, which the plan calls out: a strip-level close would
|
||||
act on whichever tab happened to be selected, and on a phone that is a mis-tap away from
|
||||
killing the wrong shell.
|
||||
Everything the phone draws above a shell. It is a bar rather than a strip because it now carries the
|
||||
two controls the collapsed chrome took with it, one at each end, with the sessions between them.
|
||||
|
||||
The hairline down its left edge is not decoration. The two targets are flush inside one
|
||||
pill, so without a visible seam there is nothing telling a thumb where "switch to this
|
||||
shell" stops and "end it" starts.
|
||||
-->
|
||||
<Button Classes="row" MinHeight="42" Width="44" Padding="0" CornerRadius="0,11,11,0"
|
||||
HorizontalContentAlignment="Center"
|
||||
BorderBrush="{StaticResource BorderMid}" BorderThickness="1,0,0,0"
|
||||
Command="{Binding $parent[views:TerminalScreen].((vm:MainWindowViewModel)DataContext).CloseTabCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<TextBlock Text="×" Foreground="{StaticResource TextFaint}" FontSize="14" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<!-- ============ the connection line ============ -->
|
||||
<!--
|
||||
The text-size buttons live here rather than in the accessory row below, and the row is the reason:
|
||||
that one scrolls, so a key can be off-screen, and these two must not be — a terminal that is too
|
||||
small to read is exactly the state in which hunting for the control that fixes it is worst.
|
||||
|
||||
A phone cannot press Ctrl+plus. The desktop head has that chord and needs no buttons; this head has
|
||||
no keyboard to press it with, which is why the two heads differ here and nowhere else in this screen.
|
||||
Both are outside the ScrollViewer deliberately. They are the way out of this surface and the way to
|
||||
another host, and a tenth tab must not be able to push either of them off the right-hand edge.
|
||||
-->
|
||||
<!--
|
||||
The design's line here also carries a round-trip time and a forwarded port. Neither is drawn: the SSH
|
||||
library offers no RTT measurement, and nothing in this application forwards anything. What is left is
|
||||
the one fact that is real and is the one that matters — the account and endpoint actually dialled.
|
||||
-->
|
||||
<Border Grid.Row="1" Padding="16,5" Height="36"
|
||||
IsVisible="{Binding SelectedTab, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Grid ColumnDefinitions="*,Auto,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="detail" FontSize="10.5" TextTrimming="CharacterEllipsis"
|
||||
Foreground="{StaticResource TextDim}"
|
||||
VerticalAlignment="Center" Text="{Binding SelectedTab.Address}" />
|
||||
<Border Grid.Row="0" Height="52" Background="{StaticResource Chrome}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
|
||||
<!--
|
||||
Disabled at the ends rather than clamping silently. A button that keeps accepting taps and does
|
||||
nothing reads as the terminal having stopped responding, which is the one thing this screen must
|
||||
never look like.
|
||||
Back, and it goes to the page this terminal was opened over rather than to Hosts by name. The
|
||||
system back gesture already does exactly that — see PhoneShell.axaml.cs — and an arrow that
|
||||
landed somewhere else would be the second of two answers to one question.
|
||||
-->
|
||||
<Button Grid.Column="1" Classes="row" MinHeight="34" MinWidth="38" Padding="0"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding ShrinkTerminalFontCommand}"
|
||||
IsEnabled="{Binding CanShrinkTerminalFont}">
|
||||
<TextBlock Classes="mono" FontSize="13" Text="A−" />
|
||||
</Button>
|
||||
<Button Grid.Column="0" Classes="icon" Content="←" Margin="4,0,0,0"
|
||||
Command="{Binding ShowScreenCommand}" CommandParameter="{Binding Screen}" />
|
||||
|
||||
<Button Grid.Column="2" Classes="row" MinHeight="34" MinWidth="38" Padding="0" Margin="4,0,0,0"
|
||||
HorizontalContentAlignment="Center"
|
||||
Command="{Binding EnlargeTerminalFontCommand}"
|
||||
IsEnabled="{Binding CanEnlargeTerminalFont}">
|
||||
<TextBlock Classes="mono" FontSize="15" Text="A+" />
|
||||
</Button>
|
||||
<!--
|
||||
v2 draws these as pills rather than as a segmented strip, so the row is transparent and each
|
||||
session carries its own outline. The close cross moved inside the pill with the name, which is
|
||||
what makes it read as one object you can dismiss rather than as two adjacent targets.
|
||||
-->
|
||||
<ScrollViewer Grid.Column="1" HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled">
|
||||
<ItemsControl ItemsSource="{Binding Tabs}" Margin="6,0" VerticalAlignment="Center">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="6" /></ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TerminalTabViewModel">
|
||||
<!--
|
||||
44 tall, where the session pills on the shell strip are 34. The difference is the close
|
||||
cross: a pill you only select can be chip-sized, and a pill containing the control that ends
|
||||
a shell cannot. This head's rule is 44 and this is the one control on the phone that is both
|
||||
destructive and has no confirmation and no undo — see CloseTabAsync, which ends the session
|
||||
the moment it is pressed.
|
||||
-->
|
||||
<Border Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||||
BorderThickness="1" CornerRadius="11" Height="44">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Button Classes="row" MinHeight="42" Padding="13,0" CornerRadius="11"
|
||||
Command="{Binding $parent[views:TerminalScreen].((vm:MainWindowViewModel)DataContext).SelectTabCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
||||
<!-- Green only while there is a shell behind it; see the same dot in PhoneShell. -->
|
||||
<Ellipse Classes="dot" Classes.live="{Binding IsLive}" Width="6" Height="6"
|
||||
VerticalAlignment="Center" />
|
||||
<TextBlock Classes="mono" FontSize="12" FontWeight="SemiBold"
|
||||
Text="{Binding Label}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<!--
|
||||
The close cross is inside the tab, which the plan calls out: a strip-level close would
|
||||
act on whichever tab happened to be selected, and on a phone that is a mis-tap away from
|
||||
killing the wrong shell.
|
||||
|
||||
The hairline down its left edge is not decoration. The two targets are flush inside one
|
||||
pill, so without a visible seam there is nothing telling a thumb where "switch to this
|
||||
shell" stops and "end it" starts.
|
||||
-->
|
||||
<Button Classes="row" MinHeight="42" Width="44" Padding="0" CornerRadius="0,11,11,0"
|
||||
HorizontalContentAlignment="Center"
|
||||
BorderBrush="{StaticResource BorderMid}" BorderThickness="1,0,0,0"
|
||||
Command="{Binding $parent[views:TerminalScreen].((vm:MainWindowViewModel)DataContext).CloseTabCommand}"
|
||||
CommandParameter="{Binding}">
|
||||
<TextBlock Text="×" Foreground="{StaticResource TextFaint}" FontSize="14" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
|
||||
<!--
|
||||
Another connection, and the three kinds this application can make. A menu rather than a straight
|
||||
jump to Hosts because SFTP and S3 used to be two taps through the bottom bar's MORE, and the bar
|
||||
is not on screen here — so the control that replaces it has to lead to all three or it has quietly
|
||||
removed two of them.
|
||||
|
||||
The desktop's own + refuses a flyout on this reasoning, in TerminalTabs.axaml: a popup dropping
|
||||
into the renderer's rectangle may or may not composite above a native child window, and that is
|
||||
not a claim to make without a screenshot. It is answered here rather than dodged — this is a sheet
|
||||
at the bottom of the screen and opening it collapses the renderer outright, exactly as the
|
||||
palette does on the desktop. Nothing is drawn over the WebView.
|
||||
-->
|
||||
<Button Grid.Column="2" Classes="icon accent" Content="+" Margin="0,0,4,0"
|
||||
Command="{Binding OpenConnectSheetCommand}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- ============ the renderer ============ -->
|
||||
<Panel Grid.Row="2">
|
||||
<Panel Grid.Row="1">
|
||||
|
||||
<!--
|
||||
The empty state, and it says what the surface is for rather than that it is empty. A phone opens
|
||||
@@ -131,7 +137,7 @@
|
||||
<StackPanel IsVisible="{Binding !HasTabs}" VerticalAlignment="Center" Margin="24" Spacing="10">
|
||||
<TextBlock Classes="title" FontSize="13" Text="NO SHELL OPEN" />
|
||||
<TextBlock Classes="body"
|
||||
Text="Choose a host and press CONNECT. A shell opened here keeps running while the app is in the background, and keeps running after the keychain is locked — a notification says so for as long as one is alive." />
|
||||
Text="Press + above, or choose a host and press CONNECT. A shell opened here keeps running while the app is in the background, and keeps running after the keychain is locked — a notification says so for as long as one is alive." />
|
||||
</StackPanel>
|
||||
|
||||
<!--
|
||||
@@ -140,10 +146,17 @@
|
||||
hold the application still any more, so there is a stretch in which a tab is selected and there is
|
||||
nothing yet to render in it. A phone needs it more than a desktop does: mobile links are slower, and
|
||||
the alternative is a black rectangle.
|
||||
|
||||
It carries the address, which is where that fact went when the connection line was folded into the
|
||||
bar above. This is the moment it is worth reading — what is being dialled, before anything has
|
||||
answered — and once a shell is open its own prompt says the same thing more accurately than a header
|
||||
derived from the keychain ever did.
|
||||
-->
|
||||
<StackPanel IsVisible="{Binding IsConnectingShowing}" VerticalAlignment="Center" Margin="24"
|
||||
Spacing="10">
|
||||
<TextBlock Classes="title" FontSize="13" Text="{Binding SelectedTab.Label}" />
|
||||
<TextBlock Classes="detail" FontSize="11" Foreground="{StaticResource TextDim}"
|
||||
TextWrapping="Wrap" Text="{Binding SelectedTab.Address}" />
|
||||
<TextBlock Classes="body" Text="{Binding SelectedTab.Status}" />
|
||||
<Button Classes="row" MinHeight="44" Padding="14,0" HorizontalAlignment="Left"
|
||||
Command="{Binding CloseTabCommand}" CommandParameter="{Binding SelectedTab}">
|
||||
@@ -153,13 +166,14 @@
|
||||
|
||||
<!--
|
||||
Collapsed rather than merely covered when there is no pane to show. On Windows this control is a
|
||||
native child window that composites above everything Avalonia draws, which is why the desktop head
|
||||
native child view that composites above everything Avalonia draws, which is why the desktop head
|
||||
hides it explicitly; whether Android's WebView does the same is recorded as unverified in
|
||||
docs/android-port.md. Hiding it either way costs nothing and is correct under both answers.
|
||||
|
||||
IsTerminalShowing rather than HasTabs, which are no longer the same question: a tab that is still
|
||||
connecting has no pane, and showing the renderer for it would show the previous session's output
|
||||
under the name of a machine nothing has connected to yet.
|
||||
under the name of a machine nothing has connected to yet. It is also what the connect sheet turns
|
||||
off — see MainWindowViewModel.IsTerminalShowing.
|
||||
|
||||
v2 insets this behind a 14-pixel radius. Not done, and not an oversight: this is a native child view
|
||||
composited above everything Avalonia draws, so a rounded Border behind it clips nothing — the
|
||||
@@ -178,13 +192,135 @@
|
||||
then release, because holding a modifier while typing is not possible one-thumbed.
|
||||
-->
|
||||
<!-- Only with a pane to type into: the keys send bytes at a session, and a connecting tab has none. -->
|
||||
<Border Grid.Row="3" IsVisible="{Binding IsTerminalShowing}" Height="50">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
||||
<StackPanel x:Name="AccessoryKeys" Orientation="Horizontal" Spacing="5" Margin="12,0"
|
||||
VerticalAlignment="Center" />
|
||||
</ScrollViewer>
|
||||
<Border Grid.Row="2" IsVisible="{Binding IsTerminalShowing}" Height="50">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
|
||||
<ScrollViewer Grid.Column="0" HorizontalScrollBarVisibility="Auto"
|
||||
VerticalScrollBarVisibility="Disabled">
|
||||
<StackPanel x:Name="AccessoryKeys" Orientation="Horizontal" Spacing="5" Margin="12,0"
|
||||
VerticalAlignment="Center" />
|
||||
</ScrollViewer>
|
||||
|
||||
<!--
|
||||
The text-size buttons, pinned at this row's right-hand end rather than scrolling with the keys
|
||||
beside them.
|
||||
|
||||
They used to have a row of their own above the terminal, on the argument that the accessory row
|
||||
scrolls and these two must never be off-screen — a terminal too small to read is exactly the state
|
||||
in which hunting for the control that fixes it is worst. That argument is answered rather than
|
||||
abandoned: outside the ScrollViewer they cannot scroll away, and the row they had costs 36 pixels
|
||||
on a surface this change exists to give back.
|
||||
|
||||
A phone cannot press Ctrl+plus. The desktop head has that chord and needs no buttons; this head
|
||||
has no keyboard to press it with, which is why the two heads differ here and nowhere else in this
|
||||
screen.
|
||||
|
||||
Disabled at the ends rather than clamping silently. A button that keeps accepting taps and does
|
||||
nothing reads as the terminal having stopped responding, which is the one thing this screen must
|
||||
never look like.
|
||||
-->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="5" Margin="8,0,12,0"
|
||||
VerticalAlignment="Center">
|
||||
<Border Width="1" Height="26" Background="{StaticResource Border}" Margin="0,0,3,0"
|
||||
VerticalAlignment="Center" />
|
||||
|
||||
<Button Classes="row" MinHeight="38" Height="38" MinWidth="40" Padding="0" CornerRadius="9"
|
||||
HorizontalContentAlignment="Center"
|
||||
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||||
BorderThickness="1"
|
||||
Command="{Binding ShrinkTerminalFontCommand}"
|
||||
IsEnabled="{Binding CanShrinkTerminalFont}">
|
||||
<TextBlock Classes="mono" FontSize="13" Text="A−" />
|
||||
</Button>
|
||||
|
||||
<Button Classes="row" MinHeight="38" Height="38" MinWidth="40" Padding="0" CornerRadius="9"
|
||||
HorizontalContentAlignment="Center"
|
||||
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
||||
BorderThickness="1"
|
||||
Command="{Binding EnlargeTerminalFontCommand}"
|
||||
IsEnabled="{Binding CanEnlargeTerminalFont}">
|
||||
<TextBlock Classes="mono" FontSize="15" Text="A+" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- ============ the connect sheet ============ -->
|
||||
<!--
|
||||
Follows the add sheet on HostsScreen, which follows HostKeySheet: a scrim, a bottom-aligned panel with
|
||||
the top two corners rounded, and a grab handle that is decoration. Dismissible, like the add sheet and
|
||||
unlike the host-key one — "which kind of connection" has no wrong answer and no answer at all is one
|
||||
of them.
|
||||
|
||||
It lives here rather than in PhoneShell for the reason the add sheet lives in its own screen: nothing
|
||||
but this surface raises it. The scrim reaching only the screen area is not a compromise here the way
|
||||
it was there — the bottom bar is collapsed while a terminal is showing, so the screen area is the
|
||||
display.
|
||||
|
||||
Every row navigates away from the terminal. That is not a side effect of the menu, it is the menu:
|
||||
each of the three destinations is a picker, and the shell they open lands back on this surface as a
|
||||
new tab in the bar above.
|
||||
-->
|
||||
<Panel IsVisible="{Binding IsConnectSheetOpen}">
|
||||
|
||||
<!--
|
||||
Declared before the sheet so the sheet draws over it: a Panel stacks its children in declaration
|
||||
order. See the .scrim style for why a tap on it must not light anything up.
|
||||
-->
|
||||
<Button Classes="scrim" Command="{Binding CloseConnectSheetCommand}" />
|
||||
|
||||
<Border VerticalAlignment="Bottom" Background="{StaticResource Panel}"
|
||||
BorderBrush="{StaticResource BorderMid}" BorderThickness="0,1,0,0"
|
||||
CornerRadius="22,22,0,0" Padding="20,18,20,16">
|
||||
<StackPanel Spacing="0">
|
||||
|
||||
<Border Width="38" Height="4" CornerRadius="2" Background="{StaticResource BorderMid}"
|
||||
HorizontalAlignment="Center" Margin="0,0,0,16" />
|
||||
|
||||
<TextBlock Classes="title" Text="CONNECT" FontSize="13" />
|
||||
|
||||
<Button Classes="row" Margin="0,10,0,0" Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Hosts}">
|
||||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Connect" />
|
||||
<TextBlock Classes="detail" FontSize="10.5"
|
||||
Text="Another shell, on this host or any other in the keychain." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
These two go through ShowFiles rather than ShowScreen, as the MORE hub's own rows do: one screen
|
||||
over one view model, and which kind of remote it offers is the thing being chosen. It can refuse
|
||||
— there is a single transfer session behind both — and refusing lands on the screen the open one
|
||||
belongs to with a sentence saying why, which is a better place to read it than a sheet that has
|
||||
just closed.
|
||||
-->
|
||||
<Button Classes="row" Command="{Binding ShowFilesCommand}"
|
||||
CommandParameter="{x:Static vm:RemoteKind.Host}">
|
||||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Connect via SFTP" />
|
||||
<TextBlock Classes="detail" FontSize="10.5" Text="Browse a host's files." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Classes="row" Command="{Binding ShowFilesCommand}"
|
||||
CommandParameter="{x:Static vm:RemoteKind.Bucket}">
|
||||
<StackPanel Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Connect via S3" />
|
||||
<TextBlock Classes="detail" FontSize="10.5"
|
||||
Text="Objects in an S3-compatible bucket from the keychain." />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<Button Classes="secondary" Content="CANCEL" Margin="0,12,0,0"
|
||||
Command="{Binding CloseConnectSheetCommand}" />
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Panel>
|
||||
|
||||
</Panel>
|
||||
|
||||
</UserControl>
|
||||
|
||||
Reference in New Issue
Block a user