diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/MainScreen.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/MainScreen.kt index 4c65b18..0423f36 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/MainScreen.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/MainScreen.kt @@ -1,7 +1,6 @@ package com.example.aiapp import androidx.activity.compose.BackHandler -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize @@ -99,7 +98,10 @@ fun MainScreen( // keep its own line. They sit on the title's row because they act on the whole // screen -- everything below this row is one tab's business, and a control belongs // with the thing it acts on. - Row(horizontalArrangement = Arrangement.spacedBy(GLYPH_BUTTON_GAP)) { + // Flush against each other: a glyph button carries its own padding, so two of them + // side by side already have two rings between their marks and one ring plus this + // row's padding to the screen edge. + Row { GlyphButton(REFRESH_GLYPH, "Refresh", { refreshToken++ }) GlyphButton(SETTINGS_GLYPH, "Settings", onSettings) } diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/NerdIcons.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/NerdIcons.kt index 16eaec8..fa50b37 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/NerdIcons.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/NerdIcons.kt @@ -107,21 +107,48 @@ val BELL_GLYPH = glyph(0xF009A) val USAGE_GLYPH = glyph(0xF201) /** - * The size of a glyph button's box, which is the size of the glyph itself. + * The size an icon draws at beside a line of text. * - * Not the 48dp of a default `IconButton`: that box centres its drawing inside itself, so aligning - * the box against a title aligns nothing a reader can see, and the pressed-state ripple lands on - * the box rather than on the mark. A box that *is* the glyph aligns like any other content and - * takes its ripple with it. The cost is the touch target -- `minimumInteractiveComponentSize` is - * applied inside the caller's modifier, so a size set here wins over it. + * 17 rather than the 20 it was while the font was the proportional face. A glyph there filled at + * most 0.83 em of its point size and most filled a good deal less, so the number was standing in + * for the headroom above the tallest one; in the Mono face every glyph fills its em exactly, and + * keeping 20 would have made every icon in the app step up by a fifth for no reason anybody asked + * for. This is what the largest of them already drew at. */ -private val GLYPH_BUTTON_SIZE = 28.dp +private val GLYPH_SIZE = 17.sp /** - * Restores the separation the 48dp boxes used to provide, now that the boxes are the size of what - * they draw: two of these plus the gap come to the same 48dp centre-to-centre spacing. + * The same measurement in dp: a glyph's em box is its point size, and a layout is laid out in dp. */ -val GLYPH_BUTTON_GAP = 48.dp - GLYPH_BUTTON_SIZE +private val GLYPH_EXTENT = GLYPH_SIZE.value.dp + +/** + * The square a glyph button occupies: the mark, plus the same ring of padding on all four sides. + * + * The ring is the whole spacing rule. Every gap around a header icon comes out of it -- one ring to + * the screen edge, two where a button meets its neighbour -- so nothing outside has to add a gap of + * its own, and a mark cannot end up further from the button beside it than from the edge of the + * screen. That is what it was: the box was the size of the mark (28dp) and the separation was + * bolted on beside it, which left the two header icons 31dp apart and the outer one 14dp from the + * edge, so a pair that acts on one screen read as two unrelated marks with one falling off it. + * + * 40dp is Material's own icon-button state layer, and it is also what the pressed-state ripple + * draws: at 28dp that circle was inscribed in the mark's own corners. The touch target grows with + * it, from well under the platform's 48dp minimum to within 8dp of it. + */ +private val GLYPH_BUTTON_SIZE = 40.dp + +/** + * The ring itself, for putting something that is *not* a glyph button next to one -- a title beside + * a back arrow. + * + * Two glyph buttons need nothing between them: each brings its own ring and the two add up, which + * is why a row of them sets no spacing. Text brings none, so the second ring has to be asked for. + * Without it the pressed-state circle, which fills the whole square, arrives at the first letter of + * the title -- and the gap a reader sees between the mark and that title is then half the one + * between the two marks at the other end of the same row. + */ +val GLYPH_BUTTON_MARGIN = (GLYPH_BUTTON_SIZE - GLYPH_EXTENT) / 2 /** * A glyph you can press: the icon equivalent of a `TextButton`. @@ -166,16 +193,17 @@ fun Glyph( colour: Color = MaterialTheme.colorScheme.primary, size: TextUnit = GLYPH_SIZE, ) { - Text(glyph, fontFamily = NerdIcons, fontSize = size, color = colour, modifier = modifier) + // Line height of the point size, which for this font is the square the glyph draws in: its + // ascent and descent add up to exactly one em, and every glyph in the Mono face fills that em. + // Left to the inherited body style the line box was 24sp tall around a 17sp-wide mark, so a + // glyph took a seventh more vertical space than horizontal wherever one is drawn without a box + // around it -- and where there is a box, that leading is what its padding is measured through. + Text( + glyph, + fontFamily = NerdIcons, + fontSize = size, + lineHeight = size, + color = colour, + modifier = modifier, + ) } - -/** - * The size an icon draws at beside a line of text. - * - * 17 rather than the 20 it was while the font was the proportional face. A glyph there filled at - * most 0.83 em of its point size and most filled a good deal less, so the number was standing in - * for the headroom above the tallest one; in the Mono face every glyph fills its em exactly, and - * keeping 20 would have made every icon in the app step up by a fifth for no reason anybody asked - * for. This is what the largest of them already drew at. - */ -private val GLYPH_SIZE = 17.sp diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt index 1462ddc..61630f5 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SessionScreen.kt @@ -1389,10 +1389,12 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: () Column(Modifier.fillMaxSize()) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp), + modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 4.dp), ) { GlyphButton(BACK_GLYPH, "Back", onBack) - Spacer(Modifier.width(8.dp)) + // A ring's worth, which is what the arrow already keeps on its other three sides -- + // the pair of glyph buttons at the far end of this row get theirs from each other. + Spacer(Modifier.width(GLYPH_BUTTON_MARGIN)) Column(Modifier.weight(1f)) { Text(title, style = MaterialTheme.typography.titleMedium) // Machine first, then what runs on it -- the same order and the same wording @@ -1424,7 +1426,7 @@ fun SessionScreen(settings: ServerSettings, summary: SessionSummary, onBack: () // yellow or red near a limit -- and the theme's plain control colour whenever there // is no measurement, since blue is the low end of the scale here and would read as // "checked, and fine" about a machine nobody could reach. - Row(horizontalArrangement = Arrangement.spacedBy(GLYPH_BUTTON_GAP)) { + Row { GlyphButton( USAGE_GLYPH, "Usage", diff --git a/app/androidApp/src/main/kotlin/com/example/aiapp/SettingsScreen.kt b/app/androidApp/src/main/kotlin/com/example/aiapp/SettingsScreen.kt index 3714478..bb9c216 100644 --- a/app/androidApp/src/main/kotlin/com/example/aiapp/SettingsScreen.kt +++ b/app/androidApp/src/main/kotlin/com/example/aiapp/SettingsScreen.kt @@ -87,7 +87,7 @@ fun SettingsScreen( // a capability being withheld but a promise it could not keep. if (onBack != null) { GlyphButton(BACK_GLYPH, "Back", onBack) - Spacer(Modifier.width(8.dp)) + Spacer(Modifier.width(GLYPH_BUTTON_MARGIN)) } Text( "Server",