diff --git a/src/main/kotlin/ui/ChannelScreen.kt b/src/main/kotlin/ui/ChannelScreen.kt index c2959bd..c05d355 100644 --- a/src/main/kotlin/ui/ChannelScreen.kt +++ b/src/main/kotlin/ui/ChannelScreen.kt @@ -1,3 +1,5 @@ +@file:OptIn(androidx.compose.ui.text.ExperimentalTextApi::class) + package ui import androidx.compose.animation.core.animateFloatAsState @@ -847,14 +849,30 @@ fun MessageScrollback( // then converts that now-stale pixel value using the NEW density, so the // two silently disagreed until the window (and this remember scope) was // recreated by closing and reopening it. - val lineHeightPx = remember(displaySettings.fontSize, displaySettings.fontId, density) { - textMeasurer.measure( - text = "Mg", - style = androidx.compose.ui.text.TextStyle( - fontFamily = AppFonts.familyFor(displaySettings.fontId), - fontSize = displaySettings.fontSize.sp + // The buffer text style gets extra vertical air on top of the font's own natural + // line height (every bundled font has different metrics, so a fixed multiplier + // would loosen some fonts and tighten others). The same style is used to measure + // the line height below AND to draw every row, so the whole-line snapping stays + // exactly in step with what is on screen. LineHeightStyle with Trim.None and a + // centered alignment makes each line exactly lineHeight tall, with the extra + // space split above and below the text. + val bufferTextStyle = remember(displaySettings.fontSize, displaySettings.fontId, density) { + val base = androidx.compose.ui.text.TextStyle( + fontFamily = AppFonts.familyFor(displaySettings.fontId), + fontSize = displaySettings.fontSize.sp + ) + val naturalPx = textMeasurer.measure(text = "Mg", style = base).size.height + val spacedLine = with(density) { naturalPx.toSp() } + (displaySettings.fontSize * LINE_SPACING_EXTRA).sp + base.copy( + lineHeight = spacedLine, + lineHeightStyle = androidx.compose.ui.text.style.LineHeightStyle( + alignment = androidx.compose.ui.text.style.LineHeightStyle.Alignment.Center, + trim = androidx.compose.ui.text.style.LineHeightStyle.Trim.None ) - ).size.height + ) + } + val lineHeightPx = remember(bufferTextStyle, density) { + textMeasurer.measure(text = "Mg", style = bufferTextStyle).size.height } val lineHeightDp = with(density) { lineHeightPx.toDp() } // Ruled out (see task: newest-message-missing investigation) as the @@ -885,10 +903,7 @@ fun MessageScrollback( // offset correction through stickToBottom's own effect instead of a // second independent one. - val textStyle = androidx.compose.ui.text.TextStyle( - fontFamily = AppFonts.familyFor(displaySettings.fontId), - fontSize = displaySettings.fontSize.sp - ) + val textStyle = bufferTextStyle val selection = selectionState.selection Column(modifier = Modifier.fillMaxSize()) { @@ -1114,6 +1129,11 @@ fun MessageScrollback( } } +// Extra vertical space added to every buffer line, as a fraction of the font size, on top of +// the font's natural line height. 0.30 is a modest loosening; raise it for more air between +// lines (0.45 is roomy), lower it for less. Applies to every font in the font list. +private const val LINE_SPACING_EXTRA = 0.30f + // Reports this element's height as a whole number of text lines ([linePx] each): rounded up // for image slots (the picture is never cut), to the nearest line for text rows (a wrapped // message can measure a pixel or two off an exact multiple). Content is drawn from the top.