Clip a wrapped title rather than ellipsise it (#267)

Wrapping at word boundaries brought the "…" back with it, which is the
half of #164 that was worth keeping: on a chip this narrow the ellipsis
costs a couple of readable characters. A wrapped title now ends at the
last whole word it could fit and clips there, spending none.

Only a single RTL line still needs the ellipsis — softWrap off clips to
the node's left edge, which in RTL is the string's end. Wrapping turns
softWrap on, which cuts the logical end in both directions, so more than
one line needs no exception.
This commit is contained in:
2026-09-07 14:59:40 +02:00
parent 775ba400a9
commit b34dc31d32
2 changed files with 16 additions and 18 deletions

View File

@@ -11,23 +11,21 @@ data class EventTitleOverflow(val overflow: TextOverflow, val softWrap: Boolean)
/**
* Overflow for an event chip's title (#164). Chips are narrow enough that the
* "…" costs a couple of readable characters, so the title runs to the chip's
* edge and clips mid-glyph instead.
* edge and clips instead — mid-glyph on one line, at the last whole word it
* could fit on several.
*
* Two cases keep the ellipsis:
*
* - **[rtl].** With `softWrap` off Compose lays the line out at its full
* intrinsic width and clips to the node's left edge, which in RTL is the *end*
* of the string — an Arabic title would lose its beginning. The ellipsis
* truncates at the logical end in both directions.
* - **More than one line** ([singleLine] false). Wrapping needs `softWrap` on,
* and clipping with it on breaks the last line at the last whole word — less
* title than the ellipsis showed, not more.
* One case keeps the ellipsis: a single **[rtl]** line. With `softWrap` off
* Compose lays the line out at its full intrinsic width and clips to the node's
* left edge, which in RTL is the *end* of the string — an Arabic title would
* lose its beginning. The ellipsis truncates at the logical end in both
* directions. Wrapping needs `softWrap` on, which clips the right end either
* way, so more than one line needs no such exception.
*/
fun eventTitleOverflowFor(rtl: Boolean, singleLine: Boolean): EventTitleOverflow =
if (rtl || !singleLine) {
EventTitleOverflow(TextOverflow.Ellipsis, softWrap = true)
} else {
EventTitleOverflow(TextOverflow.Clip, softWrap = false)
when {
!singleLine -> EventTitleOverflow(TextOverflow.Clip, softWrap = true)
rtl -> EventTitleOverflow(TextOverflow.Ellipsis, softWrap = true)
else -> EventTitleOverflow(TextOverflow.Clip, softWrap = false)
}
/** [eventTitleOverflowFor] against the current layout direction. */

View File

@@ -25,16 +25,16 @@ class EventTitleOverflowTest {
}
@Test
fun `a multi-line block keeps the ellipsis, since wrapping needs softWrap`() {
fun `a multi-line block wraps whole words and clips, spending none on a dot`() {
val result = eventTitleOverflowFor(rtl = false, singleLine = false)
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
assertThat(result.overflow).isEqualTo(TextOverflow.Clip)
assertThat(result.softWrap).isTrue()
}
@Test
fun `multi-line in RTL keeps the ellipsis too`() {
fun `multi-line in RTL clips too, since softWrap cuts the logical end`() {
val result = eventTitleOverflowFor(rtl = true, singleLine = false)
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
assertThat(result.overflow).isEqualTo(TextOverflow.Clip)
assertThat(result.softWrap).isTrue()
}
}