Public Access
Six defects, all found by running this head on an emulator against a local API rather than by reading it. They are in one commit because two of the files carry several of them and splitting those would mean hand-editing patches; each is argued at its own site. ── the keypad, which is most of this ─────────────────────────────────────────────────────────────────── It was eleven 30-by-44 keys in a horizontal ScrollViewer, and it was wrong twice over. A pan and a press begin with the same few pixels of travel, so a thumb coming down at an angle — which is how a thumb comes down — was read as the start of a pan and the press was never delivered; the row slid slightly instead. And 30 tall is well under the 48 Android asks for, so the presses that did arrive were hard to aim. The arrows, the keys pressed most and in sequence, were the ones parked past the right-hand edge. Termux, Termius, JuiceSSH, ConnectBot and Blink all ship this row and not one of them scrolls it: they divide the width evenly and add a second row when the set does not fit. That is not convention for its own sake — a key whose position is fixed can be hit without looking, which is the whole point of a modifier row used while reading the output above it. So: two UniformGrid rows, every key an equal share of the width, nothing scrollable, 40 tall. Every key kept. Arrows on the lower row where the thumb rests, reordered to ← ↓ ↑ → so the pairs that undo each other are neighbours. It costs about 51 pixels of terminal, four or five lines, and that is the right trade: the row that was cheaper did not work. The keys also gain a pressed state, which they had none of. They are Focusable="False" so that using one does not take the keyboard off the terminal, and a button that never takes focus never shows the adorner Fluent would otherwise give it — leaving no feedback at all on a control whose only job is to be pressed. Their looks move from assignments in the code-behind to Button.hotkey, which is what lets a style say :pressed, and the Ctrl latch becomes a class rather than two brushes written by hand. ── and five smaller ones ──────────────────────────────────────────────────────────────────────────────── The keyboard's inset was never cleared when the app was resumed without the keyboard it was applied for. Android takes the keyboard away as the activity stops and Avalonia's input pane raises no StateChanged, the pane going with the window rather than closing in front of one — so the interface came back holding a whole keyboard's height of empty canvas. 838 of 2340 pixels on a Pixel 5, on every screen, until something opened and closed the keyboard. MainActivity now raises PhoneEnvironment.Resumed and PhoneShell re-asks the pane. It asks rather than assuming zero: a permission prompt or the biometric sheet can hand the activity back with the keyboard genuinely still up, and flattening the inset there would strand the focused box under a real one. TextBlock.detail never wrapped, where .body beside it always has. A block that neither wraps nor trims lays out at its full single-line width and the parent clips it, with no ellipsis to say anything is missing — so the add sheet read "A machine to connect to, filed under a group if you" and stopped. Which of the fifty-odd .detail blocks overflows is a function of width and font scale rather than of the string, so this is a style rather than an attribute on the handful visibly wrong on one phone. The nine that genuinely want one line now say NoWrap beside their TextTrimming, which is the pairing that was always meant. The two port boxes are NumericUpDowns and were the only unthemed controls on this head — Fluent's border and fill, next to a TextBox.field sharing their row. App.axaml has styled NumericUpDown for the desktop all along; this head never had one. Three buttons classed "icon accent" were painting Fluent's own Button.accent, #6275AC, a blue in no palette here. The desktop overrides that style and this head did not, so the same class read correctly there and not here. The host key sheet said "First contact with 10.0.2.2 · 2222 . Nothing in this keychain…" — Avalonia puts a space between adjacent Runs and the last one began with the full stop. A gap before a full stop, on the one dialogue whose whole job is to be read carefully before it is answered. ── checks ────────────────────────────────────────────────────────────────────────────────────────────── manual-checks gains 10.4a and 11.10f. 10.4 already covered dismissing the keyboard with back or the chevron but not leaving the app with it up, which is the path the inset bug shipped through; 10.4a covers both directions, since a fix that flattened the inset on every resume would pass one half and fail the other. 11.10f guards the keypad against a ScrollViewer being put back around it.
739 lines
43 KiB
XML
739 lines
43 KiB
XML
<Styles xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
|
|
<!--
|
|
The phone's own control styles. The desktop head has an equivalent block in its App.axaml and the two
|
|
deliberately do not share one: the palette is shared because a colour must not drift, but a control's
|
|
size is a property of the input device. Every touch target here is at least 44 effective pixels tall,
|
|
which is the number the desktop's 28-pixel rows would fail.
|
|
|
|
Classes rather than implicit styles for the buttons, because the shell needs three that differ in
|
|
meaning rather than in degree: the accent one is what the screen wants you to do, the outlined one is
|
|
the alternative, and the red one is the one that costs something.
|
|
|
|
── v2 ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
The second design rounded everything on its own ladder — 4 for a tag, 9 for a button or a pill, 10 for
|
|
a list row, 11 for the search well, 12 for a card, 14 for a block of monospaced output — and the numbers
|
|
below carried it for three passes: the radius said how big the thing was, so a 12 on a chip or a 4 on a
|
|
card read as the wrong size before it read as the wrong shape. Left as a record of that reasoning rather
|
|
than deleted, because the reasoning still holds; only the numbers it was reasoning about have moved.
|
|
|
|
── v5 ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
This pass takes the desktop's v5 ladder instead of v2's own, on the same reversal recorded for the
|
|
fonts: the phone now matches the desktop's shape as well as its face. Three rungs rather than six —
|
|
cards and sections stay 12, a button or a field is 10, a chip or a tag is 6 — collapsing v2's 9/10/11
|
|
into the one value the desktop's buttons and fields already use, and moving its 4 up to 6 and its 14
|
|
down to 12 to land on the desktop's own chip and card numbers. The ladder still says how big a thing
|
|
is before it says what shape it is; it is just a shorter ladder now, because the desktop it is copying
|
|
never drew v2's 11-radius search well or a card any rounder than a chip's neighbour a step away, and the
|
|
same case that closed the gap between 9, 10 and 11 closes the one between 12 and 14.
|
|
|
|
Not every radius on this head belongs to this ladder. The floating action button is 28 — half its own
|
|
56, which is a circle rather than a ladder rung — and the sheets stay at 22 on their top corners only,
|
|
which is a phone idiom this codebase's own bottom sheets have used since v2 and the desktop draws
|
|
nothing like. Both are documented where they are set rather than here, for the reason FAB and sheet
|
|
radii are always documented locally: a reader who only ever meets one of them should not have to find
|
|
this paragraph to learn it was deliberate.
|
|
-->
|
|
|
|
<Style Selector="Button.primary">
|
|
<Setter Property="Height" Value="48" />
|
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Foreground" Value="{StaticResource AccentInk}" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="12" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
</Style>
|
|
|
|
<!--
|
|
◆ Gradient and glow, replacing a flat Accent fill — the same swap App.axaml's Button.accent made on the
|
|
desktop, and for the same reason: this design draws every primary button top-to-bottom from a lighter
|
|
violet into the accent proper, lifted off the surface with a soft violet glow rather than a border. Both
|
|
live in Palette.axaml as AccentGradient and AccentGlow, already resolvable here because the palette is
|
|
shared — this is a resource-key swap, not new colour.
|
|
|
|
Background and BoxShadow are set here rather than as plain Setters above, because Fluent's default
|
|
button template only lets a style reach the fill and the glow through the ContentPresenter it draws
|
|
itself around — the same reason the desktop's rule targets /template/ ContentPresenter rather than the
|
|
Button. Height, radius and the rest stay ordinary Setters on Button.primary itself; only the two the
|
|
template intercepts move down here.
|
|
-->
|
|
<Style Selector="Button.primary /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource AccentGradient}" />
|
|
<Setter Property="BoxShadow" Value="{StaticResource AccentGlow}" />
|
|
</Style>
|
|
<Style Selector="Button.primary:pressed /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource AccentGradient}" />
|
|
<Setter Property="Opacity" Value="0.82" />
|
|
</Style>
|
|
|
|
<!--
|
|
Disabled is drawn as flat and unlit rather than merely dimmed. The design's CONTINUE button on the
|
|
recovery screen is disabled until the checkbox is ticked, and a user who cannot tell it is disabled
|
|
reads the screen as broken rather than as waiting for them.
|
|
|
|
BoxShadow has to be cleared here too, as "none" rather than left unset — see the remark on
|
|
Button.accent:disabled in the desktop's App.axaml for why the literal string is required and an empty
|
|
BoxShadows is not: the base rule's glow Setter is still in effect wherever a more specific one does not
|
|
override it, and a disabled primary button lit with a glow would read as wanting to be pressed.
|
|
-->
|
|
<Style Selector="Button.primary:disabled /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Raised}" />
|
|
<Setter Property="BoxShadow" Value="none" />
|
|
<Setter Property="TextElement.Foreground" Value="{StaticResource TextFaint}" />
|
|
</Style>
|
|
|
|
<Style Selector="Button.secondary">
|
|
<Setter Property="Height" Value="48" />
|
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="11.5" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
</Style>
|
|
|
|
<Style Selector="Button.danger">
|
|
<Setter Property="Height" Value="48" />
|
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource DangerSoft}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="Foreground" Value="{StaticResource Danger}" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="10.5" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
</Style>
|
|
|
|
<!--
|
|
A row in a list: the whole row is the target, and it is 54 tall because a thumb is not a mouse.
|
|
|
|
◆ VerticalContentAlignment, because that height is the whole point of this class and Avalonia's default
|
|
for content alignment is Stretch — so the content presenter stretched the caption to the full row and a
|
|
TextBlock draws its line at the TOP of what it is given. Most rows here never showed it, having a
|
|
StackPanel or a Grid of already-centred children in them, which is what made the four that did look
|
|
like four unrelated mistakes: the breadcrumb chips and the up-one-directory button on FilesScreen, and
|
|
TerminalScreen's CLOSE THIS TAB, each a bare TextBlock in a row 36 or 44 tall with no vertical padding.
|
|
Measured at those numbers, the caption sat flush against the top edge with 21 to 33 pixels below it.
|
|
|
|
The desktop head's App.axaml carries the same setter on its own shapes for the same reason, and excludes
|
|
two of them — see the remark on Button.ghost there. Nothing is excluded here: no row's content depends
|
|
on being stretched, there being no full-height strip inside any of the thirty-three, and the Grids that
|
|
stop filling hold only children that already centre themselves, so they land where they always did.
|
|
-->
|
|
<Style Selector="Button.row">
|
|
<Setter Property="MinHeight" Value="54" />
|
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderThickness" Value="0" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="Padding" Value="16,6" />
|
|
</Style>
|
|
<Style Selector="Button.row:pressed /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Hover}" />
|
|
</Style>
|
|
|
|
<!--
|
|
The 44-pixel square in a header — a back arrow, an add, an upload. Square rather than stretched because
|
|
it sits beside a title that must keep the width it has, and 44 rather than the glyph's own size because
|
|
a 17-pixel arrow is a 17-pixel target.
|
|
-->
|
|
<Style Selector="Button.icon">
|
|
<Setter Property="Width" Value="44" />
|
|
<Setter Property="Height" Value="44" />
|
|
<Setter Property="Padding" Value="0" />
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderThickness" Value="0" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="FontSize" Value="17" />
|
|
</Style>
|
|
<Style Selector="Button.icon.accent">
|
|
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
|
<Setter Property="FontSize" Value="20" />
|
|
</Style>
|
|
<!--
|
|
◆ THE FILL BELOW IS NOT DECORATION. IT IS UNDOING ONE FLUENT PUTS THERE.
|
|
|
|
`accent` is not a name this theme invented — Fluent ships a `Button.accent` style of its own, and a
|
|
button classed `icon accent` matches it. So these three buttons were being painted with Fluent's
|
|
system-accent slab, #6275AC, a blue that appears nowhere in Palette.axaml and reads as a control from
|
|
a different application sitting in this one's header. Measured off a screenshot rather than guessed:
|
|
the fill was #6275AC where the accent is #5D42DE.
|
|
|
|
The rule above could not stop it, because it sets Foreground and FontSize and Fluent's fill is on the
|
|
ContentPresenter — which is also why `Button.icon`'s own Background="Transparent" does not reach it.
|
|
App.axaml has carried the desktop's answer to exactly this since v5b: override the ContentPresenter,
|
|
not the Button. This head simply never had one, so the desktop looked right and the phone did not.
|
|
|
|
Transparent rather than the desktop's gradient, and the difference is the control rather than an
|
|
inconsistency. The desktop's Button.accent is a filled primary slab — this head has one of those too,
|
|
and it is Button.primary. `icon accent` is a 44-pixel glyph in a header row beside other glyphs; what
|
|
"accent" asks for there is the accent *colour on the mark*, which is what the rule above gives it. A
|
|
slab would make one header icon a button and its neighbours not.
|
|
|
|
Pressed is deliberately not restated: Button.icon:pressed below sets ChromeHover on every icon button
|
|
and, coming later in this file, wins over this rule for the moment a finger is down. That is the
|
|
feedback the plain icon buttons give and there is no reason this one should differ.
|
|
-->
|
|
<Style Selector="Button.icon.accent /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderThickness" Value="0" />
|
|
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
|
</Style>
|
|
<Style Selector="Button.icon.accent:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
|
</Style>
|
|
<Style Selector="Button.icon.accent:disabled /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
|
|
</Style>
|
|
<Style Selector="Button.icon:pressed /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource ChromeHover}" />
|
|
</Style>
|
|
|
|
<!--
|
|
============ one key of the terminal's accessory keypad ============
|
|
|
|
A class rather than eleven sets of attributes assigned in code, and the move out of the code-behind is
|
|
most of what makes the latched state honest: Ctrl on and Ctrl off used to be two Background values
|
|
written by hand in TerminalScreen.axaml.cs, which meant the row named two of this palette's brushes
|
|
itself and any third state would have named a third. It is `latched` here, and the code toggles a class.
|
|
|
|
── the numbers, and why they are these ─────────────────────────────────────────────────────────────────
|
|
40 tall, where the row this replaced was 30. Android asks for 48 on a touch target and every terminal on
|
|
the platform lands near it; 30 was chosen to save a strip of the terminal above and took the keypad past
|
|
the point of working, which is the wrong end of that trade. 40 with a 2-pixel margin gives 44 of pitch,
|
|
which is the same figure TextBox.field uses for a box a thumb has to hit.
|
|
|
|
HorizontalAlignment Stretch, because these live in UniformGrid cells and the cell is the share of the
|
|
width the key is entitled to. Left to Center a key would shrink to its label and "|" would be a sliver
|
|
between two wide neighbours — which is the layout the flexed row exists to stop.
|
|
|
|
── pressed has to be visible, and it was not ───────────────────────────────────────────────────────────
|
|
These keys are Focusable="False" so that using one does not take the keyboard away from the terminal
|
|
(see TerminalScreen.axaml.cs), and a button that never takes focus also never shows the focus adorner
|
|
Fluent would otherwise give it. That left a press with no feedback at all on a control whose entire job
|
|
is to be pressed while somebody is looking at the line of output above it. The pressed fill below is the
|
|
only thing that says a key was hit — and on a keypad over a remote shell, "did that send?" is a question
|
|
worth answering in the interface rather than in the scrollback.
|
|
-->
|
|
<Style Selector="Button.hotkey">
|
|
<Setter Property="Height" Value="40" />
|
|
<Setter Property="MinHeight" Value="40" />
|
|
<Setter Property="MinWidth" Value="0" />
|
|
<Setter Property="Margin" Value="2,0" />
|
|
<Setter Property="Padding" Value="0" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="Background" Value="{StaticResource Panel}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="12" />
|
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
<Setter Property="VerticalAlignment" Value="Center" />
|
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
</Style>
|
|
<Style Selector="Button.hotkey /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Panel}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
</Style>
|
|
<Style Selector="Button.hotkey:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Panel}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
</Style>
|
|
<Style Selector="Button.hotkey:pressed /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Active}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
|
|
</Style>
|
|
<!--
|
|
Latched Ctrl. The same fill a press gives, held rather than flashed — a modifier that is on and does not
|
|
look on is how somebody sends ^L to a database prompt believing they typed an l.
|
|
-->
|
|
<Style Selector="Button.hotkey.latched">
|
|
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
|
</Style>
|
|
<Style Selector="Button.hotkey.latched /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Active}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource Accent}" />
|
|
</Style>
|
|
<Style Selector="Button.hotkey:disabled /template/ ContentPresenter#PART_ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Panel}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
|
|
</Style>
|
|
<Style Selector="Button.hotkey:disabled">
|
|
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
|
|
</Style>
|
|
|
|
<!--
|
|
The floating action button the design has asked for since v2, and which this theme refused twice — on
|
|
the grounds that it would open an editor this head had not got, and that an unused style is a claim the
|
|
control exists somewhere. Both were true. The second is why the style arrives in the same commit as the
|
|
button rather than ahead of it.
|
|
|
|
56 rather than the 44 the header icons use. It is the only control on the screen that floats over
|
|
content rather than sitting in a bar, so it has no neighbouring chrome to borrow an edge from, and a
|
|
thumb reaching the bottom-right corner of a 360dp screen is doing it without looking.
|
|
|
|
Accent-filled, which it shares with Button.primary and with nothing else — and it means the same thing
|
|
in both places: the one action on the surface that is not a choice between peers. Circular by radius
|
|
rather than by a Path, so the pressed state the template draws is the same shape as the button — 28,
|
|
exactly half its own 56, which is a geometric constraint rather than a ladder rung and does not move
|
|
with the rest of this file's radii.
|
|
|
|
◆ Gradient and glow since v5, matching Button.primary rather than staying a flat fill once that one
|
|
moved. The two are the only accent-filled controls on this head and are read as one idea — "the thing
|
|
this surface wants you to do" — so a flat FAB beside a gradient primary button would be the seam this
|
|
codebase's palette file keeps warning about, one screenshot over from the button it echoes. The glow
|
|
reads as well on a floating circle as it does on a bar-anchored rectangle: if anything a control that
|
|
already floats over content earns a lift more than one sitting in a row of chrome does, so it is kept
|
|
rather than dropped for the FAB's own shape.
|
|
|
|
Still only on HOSTS. The design puts a second one on S3 and that editor genuinely does not exist yet, so
|
|
the style being here is not permission to draw one there.
|
|
-->
|
|
<Style Selector="Button.fab">
|
|
<Setter Property="Width" Value="56" />
|
|
<Setter Property="Height" Value="56" />
|
|
<Setter Property="Padding" Value="0" />
|
|
<Setter Property="BorderThickness" Value="0" />
|
|
<Setter Property="CornerRadius" Value="28" />
|
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Foreground" Value="{StaticResource AccentInk}" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="24" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
</Style>
|
|
<!-- See the remark above Button.primary's own /template/ ContentPresenter rule: BoxShadow has no home on
|
|
a Button itself, so the fill moves down here alongside it rather than staying a plain Setter. -->
|
|
<Style Selector="Button.fab /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource AccentGradient}" />
|
|
<Setter Property="BoxShadow" Value="{StaticResource AccentGlow}" />
|
|
</Style>
|
|
<Style Selector="Button.fab:pressed /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource AccentGradient}" />
|
|
<Setter Property="Opacity" Value="0.82" />
|
|
</Style>
|
|
|
|
<!--
|
|
The dimmed area behind a sheet, and a Button rather than a Border because tapping it has to mean
|
|
something — see the add sheet in HostsScreen, which is dismissible for the reason the host-key sheet is
|
|
not.
|
|
|
|
62% of Canvas, written out because the palette holds no alpha variant of a surface. The pressed rule is
|
|
not decoration: a Button's default template lights its ContentPresenter when touched, so without it the
|
|
scrim would flash a pale rectangle across the whole screen on the way to closing — which reads as the
|
|
interface glitching rather than as a tap being received.
|
|
-->
|
|
<Style Selector="Button.scrim">
|
|
<Setter Property="Background" Value="#9E05050A" />
|
|
<Setter Property="BorderThickness" Value="0" />
|
|
<Setter Property="CornerRadius" Value="0" />
|
|
<Setter Property="Padding" Value="0" />
|
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
<Setter Property="VerticalAlignment" Value="Stretch" />
|
|
</Style>
|
|
<Style Selector="Button.scrim:pointerover /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="#9E05050A" />
|
|
</Style>
|
|
<Style Selector="Button.scrim:pressed /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="#9E05050A" />
|
|
</Style>
|
|
|
|
<!--
|
|
A card: one item, one rule, one thing you can act on. The design uses it for a key, a snippet, a
|
|
transfer and the panel at the foot of the keychain, and they are all the same shape deliberately —
|
|
the screens differ in what they hold rather than in how they are built.
|
|
-->
|
|
<Style Selector="Border.card">
|
|
<Setter Property="Background" Value="{StaticResource Sidebar}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource Border}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="CornerRadius" Value="12" />
|
|
<Setter Property="Padding" Value="14,13" />
|
|
</Style>
|
|
|
|
<!--
|
|
A block of monospaced output — a snippet's command, a log, the terminal. One surface for all three, and
|
|
it is not one of the palette's chrome greys: see TerminalSurface in Palette.axaml.
|
|
-->
|
|
<Style Selector="Border.output">
|
|
<Setter Property="Background" Value="{StaticResource TerminalSurface}" />
|
|
<Setter Property="CornerRadius" Value="12" />
|
|
<Setter Property="Padding" Value="12" />
|
|
</Style>
|
|
|
|
<!--
|
|
A small monospaced tag: a vault's name, a sync badge, what is open on the files screen. Not a control —
|
|
it is never pressed — so a Border rather than a Button, and the class exists so that the four screens
|
|
drawing one cannot disagree about its padding.
|
|
-->
|
|
<Style Selector="Border.tag">
|
|
<Setter Property="Background" Value="{StaticResource Chip}" />
|
|
<Setter Property="CornerRadius" Value="6" />
|
|
<Setter Property="Padding" Value="7,2" />
|
|
<Setter Property="VerticalAlignment" Value="Center" />
|
|
</Style>
|
|
<Style Selector="Border.tag > TextBlock">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="9" />
|
|
<Setter Property="FontWeight" Value="Medium" />
|
|
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
|
</Style>
|
|
|
|
<!--
|
|
A tag that is also a button: the picker in the host editor. It borrows Border.tag's geometry rather than
|
|
inheriting it, because a Button is not a Border and Avalonia's selectors are structural — the numbers
|
|
are repeated and the two have to move together, which is why they sit adjacent.
|
|
|
|
Worn is filled, unworn is outlined, so a row of these reads as the host's tags rather than as a form
|
|
about them. 36 tall rather than 44: it is a chip, its neighbours are chips, and a row of 44-pixel pills
|
|
would be a wall. The whole chip is the target and they are spaced 6 apart, which is what keeps a miss
|
|
landing on nothing rather than on the wrong tag.
|
|
-->
|
|
<Style Selector="Button.chiptoggle">
|
|
<Setter Property="MinHeight" Value="36" />
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="CornerRadius" Value="6" />
|
|
<Setter Property="Padding" Value="11,0" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
</Style>
|
|
<Style Selector="Button.chiptoggle.worn">
|
|
<Setter Property="BorderBrush" Value="Transparent" />
|
|
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
|
</Style>
|
|
<Style Selector="Button.chiptoggle.worn /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Chip}" />
|
|
</Style>
|
|
<Style Selector="Button.chiptoggle:pressed /template/ ContentPresenter">
|
|
<Setter Property="Background" Value="{StaticResource Hover}" />
|
|
</Style>
|
|
|
|
<!-- An outlined tag, for a fact about an item rather than a name: a type, an algorithm, a count. -->
|
|
<Style Selector="Border.tag.outline">
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="Padding" Value="5,1" />
|
|
</Style>
|
|
<Style Selector="Border.tag.outline > TextBlock">
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="FontSize" Value="8.5" />
|
|
</Style>
|
|
|
|
<!--
|
|
A category chip. A RadioButton rather than a ToggleButton because the options are exclusive and a screen
|
|
reader should say so; the bullet its default template draws is removed, since the chip's own fill is
|
|
what carries the state.
|
|
|
|
Checked is a filled surface with accent *text*, not an accent fill. v2 makes that distinction
|
|
everywhere — see the remark on AccentText in Palette.axaml — and a chip is where it matters most: a row
|
|
of four solid blue lozenges is a row of four things that all look like the primary action.
|
|
|
|
◆ Its own radius rather than Border.tag's or Button.chiptoggle's, despite the name. v2 drew this control
|
|
on the ladder's button-or-pill rung rather than its tag rung — 9, not 4 — and the v5 pass carries the
|
|
same rung forward to 10 rather than to Border.tag's 6: it is a much larger control at 34 tall against a
|
|
tag's line-height, and a radius picked for a 34-pixel chip is not the one a 20-pixel tag needs, whatever
|
|
the class is called. See the remark on Phone.axaml's own ladder, above.
|
|
-->
|
|
<Style Selector="RadioButton.chip">
|
|
<Setter Property="MinHeight" Value="34" />
|
|
<Setter Property="Padding" Value="13,6" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="10.5" />
|
|
<Setter Property="FontWeight" Value="Medium" />
|
|
<Setter Property="Template">
|
|
<ControlTemplate>
|
|
<Border Name="Chip" Background="{TemplateBinding Background}"
|
|
BorderBrush="{TemplateBinding BorderBrush}"
|
|
BorderThickness="{TemplateBinding BorderThickness}"
|
|
CornerRadius="{TemplateBinding CornerRadius}"
|
|
Padding="{TemplateBinding Padding}"
|
|
MinHeight="{TemplateBinding MinHeight}">
|
|
<ContentPresenter Content="{TemplateBinding Content}"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
|
</Border>
|
|
</ControlTemplate>
|
|
</Setter>
|
|
</Style>
|
|
<Style Selector="RadioButton.chip:checked">
|
|
<Setter Property="Background" Value="{StaticResource Active}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
</Style>
|
|
|
|
<!--
|
|
No segmented control either. The design draws one over the file listing for LOCAL / REMOTE, and there is
|
|
no local side on a phone — see FilesScreen. The two-way choices that are real, the log's two logs and
|
|
the keychain's categories, are chip rows, which is the same control at the same size without a well
|
|
around it.
|
|
-->
|
|
|
|
<Style Selector="TextBox.field">
|
|
<Setter Property="Height" Value="44" />
|
|
<Setter Property="Padding" Value="14,0" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Background" Value="{StaticResource Field}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="12" />
|
|
</Style>
|
|
|
|
<!--
|
|
◆ The two port boxes, which are NumericUpDowns and were the only unthemed controls on this head.
|
|
|
|
They sit in a two-column Grid with a plain TextBox.field beside them — port next to username in the host
|
|
editor, and port next to default-user in the group editor — so the mismatch was not subtle: a bright
|
|
Fluent border and a near-black fill against this theme's #101019 and hairline, at the one place on the
|
|
screen where the two are a hand's width apart and directly comparable.
|
|
|
|
Not `Classes="field"` on the two call sites, because that class is a TextBox class and this is not a
|
|
TextBox: NumericUpDown is a ButtonSpinner wrapping one, so half the chrome being corrected belongs to
|
|
the spinner and would not take a TextBox setter. Typing the control instead also means a third port box
|
|
added later is right without anybody remembering, which is the property the desktop's own bare
|
|
`Selector="NumericUpDown"` in App.axaml has had since it was written.
|
|
|
|
Three selectors rather than one for the same reason Button.icon.accent needs two: Fluent puts the fill
|
|
and the border on template parts, and a value set on the control alone is a TemplateBinding the theme's
|
|
own wins over. The numbers are TextBox.field's, exactly — 44 tall, 10 radius, Field on BorderMid — so
|
|
the pair reads as one row of two boxes rather than as a box and a visitor.
|
|
-->
|
|
<Style Selector="NumericUpDown">
|
|
<Setter Property="Height" Value="44" />
|
|
<Setter Property="MinHeight" Value="44" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Background" Value="{StaticResource Field}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="12" />
|
|
</Style>
|
|
<Style Selector="NumericUpDown /template/ ButtonSpinner">
|
|
<Setter Property="Background" Value="{StaticResource Field}" />
|
|
<Setter Property="BorderBrush" Value="{StaticResource BorderMid}" />
|
|
<Setter Property="BorderThickness" Value="1" />
|
|
<Setter Property="CornerRadius" Value="10" />
|
|
<Setter Property="MinHeight" Value="44" />
|
|
</Style>
|
|
<!--
|
|
The inner box gives its chrome up entirely: the spinner above is drawing the border and the fill, and a
|
|
TextBox that kept its own would put a second rounded rectangle a pixel inside the first.
|
|
-->
|
|
<Style Selector="NumericUpDown /template/ TextBox">
|
|
<Setter Property="Background" Value="Transparent" />
|
|
<Setter Property="BorderThickness" Value="0" />
|
|
<Setter Property="Padding" Value="14,0" />
|
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="FontSize" Value="12" />
|
|
</Style>
|
|
|
|
<!--
|
|
Every box on this head that takes a secret, and it is a class rather than two attributes repeated five
|
|
times because the two attributes are not interchangeable and both are needed.
|
|
|
|
<b>PasswordChar is what the screen shows; ContentType is what the keyboard is told.</b> The desktop head
|
|
needs only the first — a Windows text box has no opinion about what is being typed into it. Android's
|
|
software keyboard does: left at its default it treats a passphrase box as ordinary prose, which means
|
|
the suggestion strip offers completions from everything that phone has ever been typed into, and the
|
|
IME's own learning dictionary remembers what was typed. Dots on screen and a word-suggestion bar above
|
|
them is the worst of both — the secret is hidden from the person typing it and offered to the room.
|
|
TextInputOptions.ContentType is the property the Android backend maps onto InputType, and Password is
|
|
what turns the suggestions off and keeps the entry out of the dictionary.
|
|
|
|
A field class is not implied. Two of the five boxes carry their own metrics — see LockedScreen — so this
|
|
one sets nothing about size or colour, and the other three say Classes="field secret".
|
|
-->
|
|
<Style Selector="TextBox.secret">
|
|
<Setter Property="PasswordChar" Value="•" />
|
|
<Setter Property="TextInputOptions.ContentType" Value="Password" />
|
|
</Style>
|
|
|
|
<!--
|
|
The live dot, and the class name is the same one the desktop sidebar uses so the two heads cannot
|
|
drift on what green means: a terminal is open on this host right now. Deliberately not reachability —
|
|
nothing pings anything, and a dot meaning "up" would be a claim the application never checks.
|
|
|
|
Live rather than Accent since v2. The accent is blue now and this is not an accent: it is a fact about
|
|
a host. See the remark on Live in Palette.axaml.
|
|
-->
|
|
<Style Selector="Ellipse.dot">
|
|
<Setter Property="Width" Value="7" />
|
|
<Setter Property="Height" Value="7" />
|
|
<Setter Property="Fill" Value="{StaticResource TextGhost}" />
|
|
</Style>
|
|
<Style Selector="Ellipse.dot.live">
|
|
<Setter Property="Fill" Value="{StaticResource Live}" />
|
|
</Style>
|
|
|
|
<!--
|
|
Amber, and it does not contradict the remark above. That one says green is a fact about a host rather
|
|
than an accent, and this is the colour for a fact that is not settled yet: green is what is true, purple
|
|
is what you can press, and a connection still being made is neither. The palette's own rule gives amber
|
|
to the caveat worth reading, which is exactly what this is.
|
|
|
|
Only the tab strips use it, and only for a tab with no shell behind it yet — the same amber, from the
|
|
same brush, as the track and the running step on the connecting screen, so that a tab and the screen it
|
|
opens agree about what is happening. See TerminalScreen.axaml.
|
|
-->
|
|
<Style Selector="Ellipse.dot.connecting">
|
|
<Setter Property="Fill" Value="{StaticResource Warn}" />
|
|
</Style>
|
|
|
|
<!--
|
|
Red for a shell whose connection broke, splitting the grey that used to cover every dead tab. A shell
|
|
somebody exited and a shell that was cut off are the two dead states with the least in common, and on a
|
|
phone the difference matters more than it does on a desk: a mobile link drops on its own, and the tab
|
|
that went quiet while the screen was off is the one worth dialling again. See
|
|
TerminalTabViewModel.IsDropped.
|
|
-->
|
|
<Style Selector="Ellipse.dot.dropped">
|
|
<Setter Property="Fill" Value="{StaticResource Danger}" />
|
|
</Style>
|
|
|
|
<!-- Every label, count, address and fingerprint in this design is monospace. See Palette.axaml. -->
|
|
<Style Selector="TextBlock.mono">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontSize" Value="12" />
|
|
</Style>
|
|
<Style Selector="TextBlock.label">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="FontSize" Value="9.5" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
</Style>
|
|
<!--
|
|
◆ Wraps, like .body below it, and the omission was doing real damage rather than none.
|
|
|
|
A TextBlock that neither wraps nor trims does not shrink to fit and does not report that it did not:
|
|
it lays out at its full single-line width and the parent clips whatever runs past the edge. So a
|
|
.detail long enough to overflow ended mid-word against the screen, with no ellipsis to say anything
|
|
was missing — "A machine to connect to, filed under a group if you" on the add sheet, which reads as
|
|
a sentence somebody forgot to finish rather than as text that was cut.
|
|
|
|
It is a style rather than an attribute on the ones that overflow today, because which of the fifty-odd
|
|
.detail blocks overflows is a function of the device's width and the user's font scale, not of the
|
|
string: every one of them is a candidate at 320dp that is fine at 411dp, and fixing the handful that
|
|
happen to be visibly wrong on a Pixel 5 would leave the rest to be found one narrow phone at a time.
|
|
|
|
The nine that genuinely want one line — list rows and log lines, where a wrapped detail would push the
|
|
row's own height around — now say TextWrapping="NoWrap" beside their TextTrimming, which is the pairing
|
|
that was always meant and was previously being had by accident.
|
|
-->
|
|
<Style Selector="TextBlock.detail">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
|
|
<Setter Property="FontSize" Value="10" />
|
|
<Setter Property="TextWrapping" Value="Wrap" />
|
|
</Style>
|
|
<Style Selector="TextBlock.body">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="FontSize" Value="11" />
|
|
<Setter Property="LineHeight" Value="19" />
|
|
<Setter Property="TextWrapping" Value="Wrap" />
|
|
</Style>
|
|
<Style Selector="TextBlock.title">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontSize" Value="15" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
</Style>
|
|
|
|
<!--
|
|
A screen's own name, in the header. The one place this design is not monospaced: the design sets every
|
|
heading in its sans face and keeps the mono for what is machine-shaped — an address, a fingerprint, a
|
|
count, a command. That split is worth honouring even though both resolve to substituted fonts here.
|
|
-->
|
|
<Style Selector="TextBlock.heading">
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontSize" Value="17" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
<Setter Property="VerticalAlignment" Value="Center" />
|
|
</Style>
|
|
|
|
<!--
|
|
A group heading over a list: PRODUCTION, STAGING. Letter-spaced, and quieter than the rows under it —
|
|
but TextDim rather than the design's own #565E7E. In a flat list these headings are the only thing
|
|
separating one block of hosts from the next, and at 10px with a letter-space the fainter step is under
|
|
3:1 on this background. Quieter than a host row, still readable, which is what "quieter" has to mean.
|
|
-->
|
|
<Style Selector="TextBlock.section">
|
|
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
|
<Setter Property="FontSize" Value="10" />
|
|
<Setter Property="FontWeight" Value="SemiBold" />
|
|
<Setter Property="LetterSpacing" Value="1" />
|
|
</Style>
|
|
|
|
<!--
|
|
A fingerprint, and it is a style rather than four repeated setters because the rule it encodes is a
|
|
product rule: never truncated, and always breakable across lines rather than clipped. Four screens show
|
|
one, and the day one of them clips is the day somebody approves the wrong key.
|
|
-->
|
|
<!--
|
|
◆ Declared twice, and this is the bug the rule above exists to prevent rather than a tidiness problem.
|
|
|
|
Avalonia's type selectors are exact: `TextBlock.fingerprint` does not match a SelectableTextBlock, which
|
|
derives from it. Every fingerprint on this head is selectable — you want to be able to copy one and
|
|
compare it — so with only the first selector the style applied to none of them, and a SHA256 fingerprint
|
|
rendered in the proportional font at the inherited size with TextWrapping left at NoWrap. At 360dp that
|
|
runs off the edge and clips, which is exactly the thing the comment above says must never happen.
|
|
|
|
The desktop head already carries the same pair for `mono` (App.axaml), for the same reason. Avalonia has
|
|
an `:is(TextBlock)` form that would match both in one selector; two explicit lines are used here because
|
|
the duplication is the thing worth seeing when somebody adds a third fingerprint site.
|
|
-->
|
|
<Style Selector="TextBlock.fingerprint">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontSize" Value="11" />
|
|
<Setter Property="LineHeight" Value="18" />
|
|
<Setter Property="TextWrapping" Value="Wrap" />
|
|
</Style>
|
|
<Style Selector="SelectableTextBlock.fingerprint">
|
|
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
|
<Setter Property="Foreground" Value="{StaticResource Text}" />
|
|
<Setter Property="FontSize" Value="11" />
|
|
<Setter Property="LineHeight" Value="18" />
|
|
<Setter Property="TextWrapping" Value="Wrap" />
|
|
</Style>
|
|
|
|
</Styles>
|