Let the launcher icon catch up with the mark v2 already drew everywhere else

TitleBar and PhoneShell have drawn >_ knocked out of a solid accent tile since
v2, and both say in a comment that the launcher icon draws the same thing so the
three cannot drift. The launcher was still v1: an outlined square in the accent.
The comments are true now.

On Android the tile is the background layer rather than a rounded rectangle in
the foreground, because the rounding a launcher applies is its mask. Drawing the
tile would put a second rounded shape inside the mask, clipped at the corners on
any device whose mask is not the one it was drawn for; leaving it to the mask
gets a squircle on one device and a circle on another for free. Windows has no
mask, so dodossh.ico still draws its own corners.

The monochrome layer keeps only the glyph. A themed icon drops the background,
which is where the tile now lives, and a layer filled to the edges to stand in
for it would tint to a featureless square.

The glyph is half the width of the safe zone, taken from the two headers rather
than invented -- both set >_ a little under half the tile across. It is also
markedly more legible small than the outline was: dark on accent at 16px is a
mark, where a sub-pixel hairline was a smudge.
This commit is contained in:
2026-08-03 14:45:18 +02:00
parent c7c844f7bd
commit d6cd8825fc
6 changed files with 71 additions and 68 deletions
@@ -1,25 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
The launcher mark, and it is the same mark ServerScreen and LockedScreen draw: a square
outline in the accent with >_ inside it. Redrawn as a vector rather than exported as a
bitmap so there is one geometry to change and no set of five PNG densities to forget one
of.
The launcher mark, and it is the same mark PhoneShell's header and the desktop titlebar draw:
>_ in the canvas colour on a solid accent tile. Filled rather than outlined since v2.
#5B8CFF is AccentColor from DodoSSH.Client.Shell's Theme/Palette.axaml, written out
because an Android resource cannot reference a XAML dictionary. The same duplication
colors.xml already carries for the window background, and the same rule applies: if the
palette moves, this moves with it.
The tile is not in this file. It is the background layer — @color/dodo_accent, see ic_launcher.xml
— and that is the whole trick of the filled design on Android: the rounding a launcher applies is
its mask, so letting the mask make the tile gets a squircle on one device and a circle on another
without either being drawn here. A rounded rectangle painted into this layer would be a second
rounded shape inside the first, visibly clipped at the corners on any device whose mask is not the
one it was drawn for.
108x108 with the artwork inside the middle 72 is the adaptive-icon contract — the outer
18 on each edge is what the launcher eats for masking and parallax. That 72 is a width,
though, and the mark is a square: its corners are what a circular mask reaches first. At
48 across the corners land 33.9 out against a radius of 36 and read as clipped even
though they technically clear it. 42 puts them at 29.7, which is margin one can see.
#0E1220 is AccentInk from DodoSSH.Client.Shell's Theme/Palette.axaml, written out because an
Android resource cannot reference a XAML dictionary. It equals Canvas today and is named
separately in the palette for a reason worth keeping in mind here: this is ink on the accent, not
the window behind it, and it follows AccentInk if the two ever part.
The stroke widths are the one place this deliberately departs from the screen. In the app
the box is a 1px border on 44px; scaled honestly that would be 1.0 here, and a launcher
drawing this at 48dp would render it at half a pixel and show nothing. 2.2 and 2.8 are
what keep it reading as the same hairline mark at the size it is actually looked at.
108x108 with the artwork inside the middle 72 is the adaptive-icon contract — the outer 18 on each
edge is what the launcher eats for masking and parallax. The glyph spans 36.06..71.94, which is
half the width of that 72 and centred in it. That half is taken from the headers rather than
invented: the phone draws >_ at font size 10 on a 26px tile and the titlebar at 9 on 20px, both a
little under half the tile across, and a launcher icon is looked at from further away than either.
The stroke width is the one place this deliberately departs from the screen. In the app the glyph
is a bold mono face whose stems come out near a fifth of its height; 5.4 here is nearer a quarter,
which is what keeps it reading as the same mark at the size it is actually looked at.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
@@ -27,28 +31,21 @@
android:viewportWidth="108"
android:viewportHeight="108">
<!-- The box: 42 across, centred, square-cornered as the Border in the app is. -->
<path
android:pathData="M33,33 L75,33 L75,75 L33,75 Z"
android:fillColor="#00000000"
android:strokeColor="#5B8CFF"
android:strokeWidth="2.2" />
<!-- The chevron of >_ -->
<path
android:pathData="M44.8,48.75 L51.9,54 L44.8,59.25"
android:pathData="M36.06,43.33 L49.91,53.57 L36.06,63.8"
android:fillColor="#00000000"
android:strokeColor="#5B8CFF"
android:strokeWidth="2.8"
android:strokeColor="#0E1220"
android:strokeWidth="5.4"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<!-- The underscore, on the baseline the chevron bottoms out at. -->
<path
android:pathData="M54,59.7 L63.2,59.7"
android:pathData="M54,64.68 L71.94,64.68"
android:fillColor="#00000000"
android:strokeColor="#5B8CFF"
android:strokeWidth="2.8"
android:strokeColor="#0E1220"
android:strokeWidth="5.4"
android:strokeLineCap="round" />
</vector>
@@ -4,9 +4,14 @@
the wallpaper's colours. The system tints this by its alpha and discards the colour, so
the geometry is the foreground's and white is only a way of saying "opaque here".
Note what that means for the filled design: the accent tile is the background layer, and a
themed icon drops the background entirely. So the shape that survives here is the glyph, not
the tile — which is the right way round anyway. Filling this layer to the edges to stand in
for the tile would tint to a featureless square with nothing of the mark left in it.
Worth shipping rather than leaving out: a launcher with themed icons on and no monochrome
layer to use falls back to the full-colour icon, so the one app on the home screen still
drawn in green is this one.
drawn in blue is this one.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
@@ -15,24 +20,18 @@
android:viewportHeight="108">
<path
android:pathData="M33,33 L75,33 L75,75 L33,75 Z"
android:pathData="M36.06,43.33 L49.91,53.57 L36.06,63.8"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeWidth="2.2" />
<path
android:pathData="M44.8,48.75 L51.9,54 L44.8,59.25"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeWidth="2.8"
android:strokeWidth="5.4"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:pathData="M54,59.7 L63.2,59.7"
android:pathData="M54,64.68 L71.94,64.68"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:strokeWidth="2.8"
android:strokeWidth="5.4"
android:strokeLineCap="round" />
</vector>
@@ -5,12 +5,17 @@
adaptive icons landed in 26, so there is no device this ships to that would need the
bitmaps. Density buckets exist to pick a PNG; a vector has nothing to pick between.
The background is the same @color/dodo_window the window, status bar and navigation bar
use, so the mark sits on the app's own near-black rather than on a second dark that is
almost but not quite it.
The background is the accent, and that is the tile itself rather than a backdrop for one:
the v2 mark is >_ knocked out of a solid accent square, so the square is this layer and the
launcher's mask is what rounds it. See ic_launcher_foreground.xml for why the rounding is
left to the mask instead of drawn.
It was @color/dodo_window until the mark went from outlined to filled, which is worth
knowing if a home screen still shows the dark version: a launcher caches icons, and the
cache outlives the install that changed them.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/dodo_window" />
<background android:drawable="@color/dodo_accent" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>
@@ -10,4 +10,11 @@
hidden.
-->
<color name="dodo_window">#0E1220</color>
<!--
AccentColor from the same palette, here because the launcher icon's background layer is a colour
and not a drawable. Same hand-kept duplication as above, and the same rule: if the palette moves,
this moves with it.
-->
<color name="dodo_accent">#5B8CFF</color>
</resources>