Draw an inline code chip behind the text instead of under it

The chip was the renderer's span background, and a span's background is
part of the text's own drawing: the text node paints the selection first
and the glyphs over it, so an opaque chip covered the selection and
selecting a sentence highlighted every word of it except the ones in
backticks. The previous fix let the selection show through by taking the
chip to 60% alpha, which is a compromise on both sides -- the chip is a
weaker step down from the page, and selected it reached #3C344F where the
words around it reached #776394.

There is a place that is under both, and a fenced block was already in it:
a modifier on the text rather than a style inside it. So `appendCodeChip`
takes the code span from the renderer's inline builder, keeps its style and
its space of padding either side but drops the background, and marks the
range; `LinkedText` draws those ranges in a `drawBehind`. The chip is back
to the full `rawSurface` fill (measured #11111B against a #1E1E2E page) and
a selection over it now lands at #776394, the same as the rest of the
sentence -- the fenced block's numbers exactly.

The geometry is one box per line, from the bounding boxes of the run's
first and last characters, taken as far as the line's `visibleEnd`. Not
`getPathForRange`: that is the shape of a *selection*, which runs to the
right edge of every line but the last, and a code span that wrapped left a
full-width empty chip behind on the line above -- twice in one fixture.
`visibleEnd` is the same rule the selection rectangle obeys, so the chip
stops where the selection stops instead of sticking its padding space out
past the end of a selected line.

Checked on the emulator against a fixture with chips in a heading, three
kinds of list item, a quote, a table cell and a link label, unselected and
under Select All, and a link with a chip in its label still opens. Cost,
against the same build without the change, streaming sixty paragraphs of
three chips each: measure 755ms against 776ms, record 327ms against 321ms,
transcript draw 0.22ms in both.
This commit is contained in:
iris committed 2026-09-03 23:32:17 -04:00
1 parent 9c4df43951
commit 3bb178363d
4 files changed
+119 -24

No files matched your search

@@ -368,15 +368,10 @@ private fun MarkdownRoot(
// exactly a card's own fill, so a fenced block inside a tool call had no
// background at all and one in a reply read as a step *up* out of the page.
codeBackground = rawSurface,
// The same colour, but let through. An inline span's background is part of the
// *text's* own drawing and the selection rectangle is drawn underneath it, so an
// opaque chip hides the selection completely: selecting a sentence highlighted
// every word of it except the ones in backticks, which is a difference in
// appearance the reader has no way to account for. Translucent, the selection
// shows through and the chip still reads as one step down from the page -- there
// is no way to draw it over the selection instead, since the order is the text
// node's.
inlineCodeBackground = rawSurface.copy(alpha = INLINE_CODE_ALPHA),
// The same colour. Not drawn by the renderer as a span background but by
// [LinkedText] behind the text, so a selection lands on top of it as it does on a
// fenced block -- see `appendCodeChip`.
inlineCodeBackground = rawSurface,
// The same tint a code block gets, rather than the renderer's 2%-alpha default:
// two adjacent tints that differ by a fiftieth read as one flat block on a phone,
// so the table would have had a border-less grid and nothing saying where it began.
@@ -719,12 +714,3 @@ class ParsedReplies {
ready.clear()
}
}
/**
* How much of the inline-code chip's fill is its own colour, the rest being whatever it sits on.
*
* High enough that the chip is still a clear step down from the page, low enough that a selection
* under it changes what the chip looks like. Both halves are the point: at 1.0 the chip was the
* only part of a selected sentence that did not look selected.
*/
private const val INLINE_CODE_ALPHA = 0.6f