Files
DodoSSH/src/DodoSSH.Client.App/App.axaml
T
jaap-jan 4300d917a8
ci / build and test (push) Failing after 3s
ci / android head (push) Failing after 2s
Stop making people wait for a handshake, and give the host list a pointer
Connecting held the vault's busy gate, which meant a window that did nothing visible for
as long as a machine took to answer — and against one that is merely asleep, that is the
whole timeout. The gate is gone from that one command. A tab now appears in the strip in
the same turn as the click, carrying "connecting…" rather than a pane, and the terminal's
rectangle draws a card naming the host and the address being dialled. Every other screen
stays usable, and two connections can be in flight at once.

That splits the vault's one connection event into three, carrying an attempt id, because
"which tab is this about" can no longer be answered by "the most recent one". The id also
buys the two kinds of not-connecting their different endings: a refusal stays in the strip
as a tab holding its reason, since by then the user is quite likely three screens away and
a status line they are not looking at is not where a failure should end; a host key
question takes the tab away and puts the window back on HOSTS, because the prompt is drawn
there and a tab claiming failure would be competing with the thing about to resume it.

ConnectAsync takes no CancellationToken any more, and that is load-bearing rather than
tidying. A [RelayCommand] over a method that takes one generates a command that cancels
the previous execution's token on every invocation — so asking for a second machine
silently abandoned the first, measured as the first tab disappearing with "Cancelled." the
instant the second was asked for. Giving up on a connection is closing its tab, and a
session that lands after that is adopted rather than dropped: a shell running with nothing
naming it cannot be closed at all.

A tab is marked active on IsShowing rather than IsSelected. The selection survives
navigating away — that is what makes the strip a way back to a terminal instead of a way
to lose one — so a tab lit while preferences filled the window was a second "you are here"
mark pointing at something nobody could see. The nav rail's own entries have always made
this distinction.

The host list grows the two gestures it looked like it already had. A right click selects
the row under the pointer before opening a menu of Connect, Edit and Delete — the menu is
on the list rather than in the item template, so its entries are the vault's own commands
and not a row's, and it is cancelled outright over a group heading. Dragging a host onto a
heading files it there, onto a host files it beside that one, and onto UNGROUPED takes it
out of a group; the write is one field of one host through the same repository a save
uses, refused while the editor is open because a drop is a gesture on the list and not on
a half-typed form.

Clicking a result in the palette connects, which is what a list of hosts under a search
box looks like it does. It went through the shell's own command, so the pointer and Enter
take one path.

And the files screen's two pickers followed the vault's lists once, at unlock: a host or a
bucket created afterwards could not be picked until the keychain had been locked and
opened again, with nothing on screen explaining why the machine plainly in the host list
was missing. They follow the collections now, re-finding the selection by id across the
rebuild a sync pass causes every minute.

165 shell tests and 69 layout tests green, including the connecting tab, both failure
endings, two connections at once, a connection in flight across a lock, and the right
click acting on the row under the pointer rather than on the selection. The drag itself is
in docs/manual-checks.md with the rest of phase 7 — headless Avalonia has no platform
drag, and a test that claimed to have dropped something would pass while confirming
nothing.
2026-07-31 22:59:33 +02:00

539 lines
28 KiB
XML

<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="DodoSSH.Client.App.DodoSshApp"
RequestedThemeVariant="Dark">
<!--
Dark by default rather than following the system. A terminal is a dark surface either way, and
a light chrome around a dark terminal is the worst of both: the eye adapts to the bright frame
and then cannot read the text.
-->
<Application.Resources>
<!--
The palette and the font stack, shared with the Android head. They moved into DodoSSH.Client.Shell
when there were two heads that had to agree on them; Theme/Palette.axaml carries the reasoning behind
the names, the five near-black surfaces and the font substitution.
-->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://DodoSSH.Client.Shell/Theme/Palette.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
<!--
Application-wide rather than on MainWindow, which is where the first three lived until the vault column
became its own control. A Window's styles reach its whole tree, so the move changes nothing about how
the application renders — but a control laid out on its own, as the layout harness lays the column out,
would otherwise lose them. That matters more than it looks: "hint" carries TextWrapping, and text that
wraps in the application and does not wrap in the harness makes every measured height too small, which
is the one way this kind of test lies quietly.
-->
<Style Selector="TextBlock.hint">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
<Style Selector="TextBlock.heading">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="FontSize" Value="18" />
<Setter Property="FontWeight" Value="SemiBold" />
</Style>
<Style Selector="Border.card">
<Setter Property="Background" Value="{StaticResource Chrome}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="24" />
<Setter Property="MaxWidth" Value="520" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<!--
Monospace, which in this design is not a decoration for code. Host names, fingerprints, addresses and
counts are all things people compare character by character, and a proportional font is what makes two
similar fingerprints look alike.
-->
<Style Selector="TextBlock.mono">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
</Style>
<Style Selector="SelectableTextBlock.mono">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
</Style>
<!--
The small tracked-out capitals the design uses for every section heading. Letter spacing rather than
bold: at this size weight closes the counters up and the word turns into a smudge, whereas spacing is
what makes six characters read as a label instead of as an abbreviation.
-->
<Style Selector="TextBlock.label">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="10" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="LetterSpacing" Value="1.2" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<!--
A chip: a bordered scrap of text stating one fact. The design uses them for tags, roles, scopes and
shortcut hints, which have nothing in common except that each is a noun the eye should be able to skip.
-->
<Style Selector="Border.chip">
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Padding" Value="6,2" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="Border.chip > TextBlock">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="10" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="LetterSpacing" Value="0.5" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Border.chip.accent">
<Setter Property="BorderBrush" Value="{StaticResource AccentSoft}" />
</Style>
<Style Selector="Border.chip.accent > TextBlock">
<Setter Property="Foreground" Value="{StaticResource Accent}" />
</Style>
<Style Selector="Border.chip.warn">
<Setter Property="BorderBrush" Value="{StaticResource WarnSoft}" />
</Style>
<Style Selector="Border.chip.warn > TextBlock">
<Setter Property="Foreground" Value="{StaticResource Warn}" />
</Style>
<!--
The buttons. Fluent's own chrome is a rounded grey slab, and one of those beside a hairline-bordered
chip reads as belonging to a different application — so the three shapes this design uses are declared
here rather than restyled per view.
Each sets its brushes on the content presenter, not on the button. That is where the Fluent theme puts
its own, so a Background set on the button is a TemplateBinding the theme's :pointerover rule overrides
— which shows up as a button that is styled until the pointer arrives and then is not.
-->
<Style Selector="Button.ghost /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.ghost:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderHover}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="Button.ghost:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderFaint}" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.accent /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Accent}" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Foreground" Value="{StaticResource Canvas}" />
</Style>
<Style Selector="Button.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Accent}" />
<Setter Property="Opacity" Value="0.85" />
<Setter Property="Foreground" Value="{StaticResource Canvas}" />
</Style>
<!--
A disabled accent button keeps its shape and loses its fill. Fluent's disabled state greys the
foreground and leaves the background, which on a green slab is unreadable rather than merely dim.
-->
<Style Selector="Button.accent:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderFaint}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.danger /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource DangerSoft}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="3" />
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<Style Selector="Button.danger:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DangerWash}" />
<Setter Property="BorderBrush" Value="{StaticResource Danger}" />
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<Style Selector="Button.danger:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="{StaticResource BorderFaint}" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<!-- Every button in this window is small, mono and tracked out; only the colours differ. -->
<Style Selector="Button.ghost, Button.accent, Button.danger">
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="10.5" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="LetterSpacing" Value="0.8" />
<Setter Property="Padding" Value="10,5" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<!--
Buttons that are not shaped like buttons: a group heading, a nav-rail destination, a terminal tab, a
window control. All four are clickable regions that read as part of the chrome, so they carry no
border and no fill until the pointer is over them — and all four are buttons rather than borders with
a gesture, because a button is focusable, keyboard-activatable and announced as a control.
-->
<Style Selector="Button.flat">
<Setter Property="MinHeight" Value="0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Style>
<Style Selector="Button.flat /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style Selector="Button.flat:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
</Style>
<Style Selector="Button.flat:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
</Style>
<Style Selector="Button.grouphead">
<Setter Property="Padding" Value="10,7,10,4" />
</Style>
<!-- A category in the vault's rail: the same accent strip a selected row carries, at full row height. -->
<Style Selector="Button.cat">
<Setter Property="Padding" Value="0,6,14,6" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="10" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="LetterSpacing" Value="0.5" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.cat /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.cat.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
The nav rail. The active destination is marked with an accent bar down its left edge and a wash
behind it, which is the design's whole idiom for "you are here" — the same two marks a selected list
row carries, so the window has one vocabulary for selection rather than one per control.
-->
<Style Selector="Button.nav">
<Setter Property="Padding" Value="0,15" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="9" />
<Setter Property="FontWeight" Value="SemiBold" />
<Setter Property="LetterSpacing" Value="1" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.nav /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="2,0,0,0" />
</Style>
<Style Selector="Button.nav:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="Button.nav.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="Background" Value="{StaticResource AccentWash}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
</Style>
<!--
A terminal tab. The active one is marked along its top edge rather than its left, because the strip
runs horizontally and a left bar on a row of tabs reads as a divider between them.
-->
<Style Selector="Button.tab">
<!-- Less on the right than the left: the close box lives inside the tab and brings its own margin. -->
<Setter Property="Padding" Value="12,0,7,0" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="10.5" />
<Setter Property="FontWeight" Value="Medium" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<!--
The button that opens a connection. A tab in every respect but the marks a tab carries: no active
state, because it is never the thing showing, and no right border, because it is not separating
itself from anything.
-->
<Style Selector="Button.tab.plus">
<Setter Property="Padding" Value="0" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.tab.plus /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="BorderThickness" Value="0,2,0,0" />
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
<Style Selector="Button.tab.plus:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="Background" Value="{StaticResource Raised}" />
</Style>
<Style Selector="Button.tab /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderSubtle}" />
<Setter Property="BorderThickness" Value="0,2,1,0" />
</Style>
<Style Selector="Button.tab.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Canvas}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
<Setter Property="BorderThickness" Value="0,2,0,0" />
</Style>
<!--
A pair of buttons standing in for a two-way choice, inside a pane rather than down a rail. Not the
.cat style, which stretches to fill a 176-pixel rail row and would be wrong at this width — and which
the category rail's own test counts, so borrowing it would have made this a fourth category.
-->
<Style Selector="Button.choice">
<Setter Property="Padding" Value="10,5" />
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
<Setter Property="FontSize" Value="9.5" />
<Setter Property="LetterSpacing" Value="0.5" />
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.choice /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Background" Value="{StaticResource Raised}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="Button.choice:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<Style Selector="Button.choice.active /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!-- The close box on a tab, and the window controls. Square, quiet, and red only where it means it. -->
<Style Selector="Button.close /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Button.close:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource DangerWash}" />
<Setter Property="Foreground" Value="{StaticResource Danger}" />
</Style>
<!--
The one inside a tab, as opposed to the ones in the titlebar. Rounded and small, because a square
full-height red panel inside a tab reads as a divider between two tabs rather than as part of one —
which is what it looked like while it was a sibling of the tab instead of a child.
-->
<Style Selector="Button.close.inline /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="CornerRadius" Value="3" />
</Style>
<!--
Text input. Fluent draws a filled box with a thick focus underline; this design draws a hairline field
that changes border colour, and the two do not sit together in one row.
-->
<Style Selector="TextBox">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="SelectionBrush" Value="{StaticResource AccentSoft}" />
<Setter Property="CaretBrush" Value="{StaticResource Accent}" />
<Setter Property="MinHeight" Value="28" />
<Setter Property="Padding" Value="8,4" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector="TextBox /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="TextBox:pointerover /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
</Style>
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource AccentSoft}" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="TextBox /template/ TextBlock#PART_Watermark">
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
</Style>
<!--
Lists. The rows in this design are drawn by their own templates and marked as selected with an accent
strip down the left edge, so the theme's rounded blue selection block has to go — it would sit behind
every row as a second, disagreeing highlight.
-->
<Style Selector="ListBox">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="ListBoxItem">
<Setter Property="Padding" Value="0" />
<Setter Property="MinHeight" Value="0" />
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style Selector="ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent" />
<Setter Property="CornerRadius" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource Hover}" />
</Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
</Style>
<Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
</Style>
<!--
The status dot, in one place rather than as a converter in code.
Two states and not the design's three. Green means connected — a terminal is open on this host, or a
connection to the server is held with nothing waiting to be sent. Grey means it is not. The design also
has an amber dot, which would have to mean "reachable but not connected", and nothing in this
application ever checks whether a host is reachable; a colour for a state nobody computes would be
decoration that reads as information.
A style rather than a value converter so that the two colours are the palette's, once. A converter
would have had to name #3CE88F in C#, which is how a green ends up one shade off in the fourth place
it appears.
-->
<Style Selector="Ellipse.dot">
<Setter Property="Width" Value="6" />
<Setter Property="Height" Value="6" />
<Setter Property="Fill" Value="{StaticResource TextFaint}" />
</Style>
<Style Selector="Ellipse.dot.live">
<Setter Property="Fill" Value="{StaticResource Accent}" />
</Style>
<!--
The accent strip a selected row carries, drawn by the row template rather than by the item, because the
item's presenter is the thing the theme keeps repainting.
-->
<Style Selector="Border.rowmark">
<Setter Property="Width" Value="2" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="ListBoxItem:selected Border.rowmark">
<Setter Property="Background" Value="{StaticResource Accent}" />
</Style>
<!--
The row a host is about to be dropped on. A wash rather than the accent strip a selection carries,
because it is not a selection: it says "let go here", it lasts as long as the pointer is over the row,
and it has to be legible on top of whatever that row already looks like — including the selected row,
which is often the one being dragged onto.
Set from the code-behind rather than by a binding, for the reason the whole gesture is code: the rows
are rebuilt from scratch on every filter keystroke and every sync pass, so a flag on the view model
would be state the list throws away halfway through the drag. See HostSidebar.axaml.cs.
-->
<Style Selector="ListBoxItem.droptarget /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="{StaticResource AccentWash}" />
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
<Setter Property="BorderThickness" Value="0,0,0,2" />
</Style>
<!--
A context menu, in this window's palette rather than the theme's. The Fluent default is a lighter grey
than anything else here, which on a near-black chrome reads as a dialog from another application.
-->
<Style Selector="MenuFlyoutPresenter, ContextMenu">
<Setter Property="Background" Value="{StaticResource Chrome}" />
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="MenuItem">
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
<!--
The same strip on the vault's category rail, which is buttons rather than list items — so the class
the rail sets stands in for the :selected pseudo-class.
It has to come after Border.rowmark, and that is not tidiness. Avalonia has no CSS specificity: two
class selectors matching one element carry the same priority, and the later declaration wins. Declared
above, "Background: Transparent" from Border.rowmark overrode this and the active category had no
strip at all — which looks exactly like a rail that was never given one.
-->
<Style Selector="Button.cat Border.catmark">
<Setter Property="Background" Value="Transparent" />
</Style>
<Style Selector="Button.cat.active Border.catmark">
<Setter Property="Background" Value="{StaticResource Accent}" />
</Style>
<Style Selector="ComboBox">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="MinHeight" Value="28" />
<Setter Property="FontSize" Value="12" />
</Style>
<Style Selector="NumericUpDown">
<Setter Property="Background" Value="{StaticResource Field}" />
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Foreground" Value="{StaticResource Text}" />
<Setter Property="MinHeight" Value="28" />
<Setter Property="FontSize" Value="12" />
</Style>
<!--
The foreground goes on the content presenter as well as on the control, for the same reason the
buttons' brushes do: the Fluent theme sets its own there, and a Foreground set only on the CheckBox is
a TemplateBinding the theme's own value wins over — which showed up as a label in the theme's blue-grey
beside a hint in this palette's.
-->
<Style Selector="CheckBox">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="0" />
</Style>
<Style Selector="CheckBox /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
</Style>
<Style Selector="CheckBox:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Foreground" Value="{StaticResource Text}" />
</Style>
</Application.Styles>
</Application>