Public Access
Two things v2 broke in the last commit, both found by reading resolved brushes rather than markup, and both the same mistake: Avalonia has no specificity, so the later declaration wins, and a rule that restated the base instead of excepting from it went in below the rules it was supposed to be underneath. Making a tab a pill gave it a Background of its own, which the flat tab it replaced never had. That one detail moved where the hover has to live. `Button.flat:pointerover` is declared far above and had been supplying it; the moment `Button.tab`'s template rule set a Background, it won, and every tab in the strip stopped answering the pointer. Silently — a tab that no longer lights is not a crash and not a layout change, and nothing in a suite that measures heights and reachability can see it. The `+` lost more than that. It sits below as an exception — no outline, because it is not one of the things being chosen between — and a `.tab` rule declared after it was overriding the exception itself. It drew as a filled, outlined pill identical to a tab, contradicting the comment directly above it. So the base pill and its hover come first now and the exceptions follow, which is the order the rest of this file already uses and the order the Border.rowmark note further down was written about. The `+` clears the fill as well as the border, because an exception to a rule that sets both has to say both. The titlebar's search box had the same shape of error in geometry rather than colour. The design draws it at exactly 380 and centred, and stating that as a Width on the inner Border is what made it wrong: the button around it is free to shrink when the account name or the vault chip beside it is long, and a Border that will not shrink with it arranges outside its own parent — over the name on one side and over the window buttons on the other. MaxWidth on a stretching button gives the same 380 whenever there is room and gives way when there is not. The regression test is the point of this commit rather than an afterthought. It is the only test in that suite that reads a brush, and the gap it fills is exactly the one these two went through: everything else measures rectangles. It hovers a tab through the real input path and asserts the fill changes, then asserts the `+` is neither filled like a tab nor outlined like one. Checked against the broken ordering before being kept — it fails there and passes here, which is the only thing that makes a regression test worth committing. Verified by the whole suite: 1310 tests over nineteen projects, none failing, the layout suite now 70 cases. Both heads build. Still nothing seen on a display. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZE3u99BNt6LzgTC5jhbz2
135 lines
7.7 KiB
XML
135 lines
7.7 KiB
XML
<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.TitleBar"
|
||
x:DataType="vm:MainWindowViewModel">
|
||
|
||
<!--
|
||
The window's own titlebar, because the design draws one.
|
||
|
||
The window asks Windows for no chrome at all, so everything a titlebar does has to be here: dragging,
|
||
the double-click to maximise, and three buttons. That is a real cost, and the reason it is worth paying
|
||
is that a 38-pixel grey system bar above a near-black application is the one part of the window that
|
||
would look borrowed.
|
||
|
||
It sits above the terminal rather than over it, which matters more than it looks: the terminal is a
|
||
native child window that composites above anything Avalonia draws in the same rectangle, so a titlebar
|
||
overlapping it would be painted underneath and its close button would not be clickable.
|
||
-->
|
||
|
||
<Border Height="44" Background="{StaticResource Chrome}"
|
||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,0,1"
|
||
PointerPressed="OnDrag" DoubleTapped="OnToggleMaximised">
|
||
|
||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="14,0,10,0">
|
||
|
||
<StackPanel Grid.Column="0" Orientation="Horizontal" Spacing="8" VerticalAlignment="Center">
|
||
<!--
|
||
Filled rather than outlined since v2, and rounded. The same mark the phone's header draws and the
|
||
same one the launcher icon carries, so the three cannot drift.
|
||
-->
|
||
<Border Width="20" Height="20" CornerRadius="6" Background="{StaticResource Accent}">
|
||
<TextBlock Classes="mono" Text=">_" FontSize="9" FontWeight="Bold"
|
||
Foreground="{StaticResource AccentInk}"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||
</Border>
|
||
<!--
|
||
The product's name is the one string in this bar that is not machine-shaped, so v2 sets it in the
|
||
sans face while the address, the account and the fingerprint beside it stay monospaced.
|
||
-->
|
||
<TextBlock Text="DodoSSH" FontSize="13" FontWeight="SemiBold"
|
||
Foreground="{StaticResource Text}" VerticalAlignment="Center" />
|
||
<!--
|
||
The design puts an organisation here — "dodotech / platform". There are no organisations: the
|
||
server has team tables and no endpoint that reads them, so the only name this application can
|
||
truthfully print is the one on the vault it has open. The account is beside it because a machine
|
||
can be enrolled to one account at a time and knowing which is the point of the chip.
|
||
-->
|
||
<Border Classes="chip" IsVisible="{Binding IsUnlocked}">
|
||
<TextBlock Text="{Binding Vault.VaultName}" />
|
||
</Border>
|
||
<TextBlock Classes="mono" Text="{Binding AccountName}" FontSize="10"
|
||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center"
|
||
TextTrimming="CharacterEllipsis" MaxWidth="220" />
|
||
</StackPanel>
|
||
|
||
<!--
|
||
The quick-connect box. It searches hosts and nothing else — the design's box also promises "run
|
||
command", and there is no snippet or saved-command item type for it to run. Clicking it is the same
|
||
as Ctrl+K, which is what the window binds; the design says ⌘K, and this is a Windows build.
|
||
-->
|
||
<!--
|
||
Stretch-to-a-maximum, not a fixed width. The design draws this box at exactly 380 and centred, and
|
||
stating that as a Width on the Border is what makes it wrong: the Button around it is free to
|
||
shrink when the account name or the vault chip beside it is long, and a Border that will not shrink
|
||
with it arranges outside its own parent — over the name on one side and over the window buttons on
|
||
the other. MaxWidth on the stretching button gives the same 380 whenever there is room and gives
|
||
way when there is not.
|
||
-->
|
||
<Button Grid.Column="1" Classes="flat" MaxWidth="380" Height="28" Margin="16,0"
|
||
HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
|
||
Command="{Binding ToggleSearchCommand}" IsEnabled="{Binding IsUnlocked}">
|
||
<Border Background="{StaticResource Field}" BorderBrush="{StaticResource BorderMid}"
|
||
BorderThickness="1" CornerRadius="8" Padding="10,0">
|
||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||
<TextBlock Grid.Column="0" Text="⌕" FontSize="12"
|
||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||
<!--
|
||
"Search or connect…", which is what it does: the palette connects on Enter. Not the design's
|
||
wider promise of running a command — a snippet is inserted from its own screen, and a box that
|
||
offered to run one would be offering something this palette does not do.
|
||
-->
|
||
<TextBlock Grid.Column="1" Text="Search or connect…" FontSize="12" Margin="8,0"
|
||
Foreground="{StaticResource TextFaint}" VerticalAlignment="Center" />
|
||
<Border Grid.Column="2" BorderBrush="{StaticResource BorderMid}" BorderThickness="1"
|
||
CornerRadius="4" Padding="5,1" VerticalAlignment="Center">
|
||
<TextBlock Classes="mono" Text="CTRL K" FontSize="9"
|
||
Foreground="{StaticResource TextFaint}" />
|
||
</Border>
|
||
</Grid>
|
||
</Border>
|
||
</Button>
|
||
|
||
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="10" VerticalAlignment="Center">
|
||
|
||
<!--
|
||
The design's dot is always green and always says SYNCED. This one says what is true: green only
|
||
while a connection is held and this machine's outbox is empty, and the word changes to the number
|
||
of changes still waiting, or to OFFLINE. A permanently green light is the same thing as no light.
|
||
-->
|
||
<StackPanel Orientation="Horizontal" Spacing="5" VerticalAlignment="Center"
|
||
IsVisible="{Binding IsUnlocked}"
|
||
ToolTip.Tip="Green means a connection is held and nothing this machine changed is still waiting to be sent. It does not mean a colleague's change has arrived — that is pulled on a timer.">
|
||
<Ellipse Classes="dot" Classes.live="{Binding IsFullySynced}" VerticalAlignment="Center" />
|
||
<TextBlock Classes="mono" Text="{Binding SyncLabel}" FontSize="8.5" FontWeight="SemiBold"
|
||
LetterSpacing="1" Foreground="{StaticResource TextDim}" VerticalAlignment="Center" />
|
||
</StackPanel>
|
||
|
||
<Border Width="1" Height="16" Background="{StaticResource Border}"
|
||
IsVisible="{Binding IsUnlocked}" />
|
||
|
||
<StackPanel Orientation="Horizontal" Spacing="2">
|
||
<Button Classes="flat" Width="26" Height="24" Click="OnMinimise"
|
||
ToolTip.Tip="Minimise">
|
||
<TextBlock Text="–" FontSize="12" Foreground="{StaticResource TextDim}"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||
</Button>
|
||
<Button Classes="flat" Width="26" Height="24" Click="OnToggleMaximised"
|
||
ToolTip.Tip="Maximise">
|
||
<TextBlock Text="▢" FontSize="10" Foreground="{StaticResource TextDim}"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||
</Button>
|
||
<Button Classes="flat close" Width="26" Height="24" Click="OnClose"
|
||
ToolTip.Tip="Close DodoSSH. This ends every shell it has open.">
|
||
<TextBlock Text="✕" FontSize="11" HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||
</Button>
|
||
</StackPanel>
|
||
|
||
</StackPanel>
|
||
|
||
</Grid>
|
||
|
||
</Border>
|
||
|
||
</UserControl>
|