Compare commits
1 Commits
feat/164-w
...
a481ddbd10
| Author | SHA1 | Date | |
|---|---|---|---|
| a481ddbd10 |
@@ -1,39 +0,0 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.common
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
|
||||||
|
|
||||||
/** How an event chip's title should overflow: what to pass to `Text`. */
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
fun eventTitleOverflowFor(rtl: Boolean, singleLine: Boolean): EventTitleOverflow =
|
|
||||||
if (rtl || !singleLine) {
|
|
||||||
EventTitleOverflow(TextOverflow.Ellipsis, softWrap = true)
|
|
||||||
} else {
|
|
||||||
EventTitleOverflow(TextOverflow.Clip, softWrap = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** [eventTitleOverflowFor] against the current layout direction. */
|
|
||||||
@Composable
|
|
||||||
fun eventTitleOverflow(singleLine: Boolean = true): EventTitleOverflow =
|
|
||||||
eventTitleOverflowFor(
|
|
||||||
rtl = LocalLayoutDirection.current == LayoutDirection.Rtl,
|
|
||||||
singleLine = singleLine,
|
|
||||||
)
|
|
||||||
@@ -464,13 +464,11 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier =
|
|||||||
.padding(horizontal = 4.dp, vertical = 2.dp),
|
.padding(horizontal = 4.dp, vertical = 2.dp),
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
val titleOverflow = eventTitleOverflow()
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = titleOverflow.overflow,
|
overflow = TextOverflow.Ellipsis,
|
||||||
softWrap = titleOverflow.softWrap,
|
|
||||||
color = eventInk(fill, alpha = 0.85f),
|
color = eventInk(fill, alpha = 0.85f),
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.semantics.contentDescription
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
import androidx.compose.ui.semantics.customActions
|
import androidx.compose.ui.semantics.customActions
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -93,7 +94,6 @@ import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventDragAllowed
|
import de.jeanlucmakiola.calendula.ui.common.eventDragAllowed
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventTitleOverflow
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
||||||
import de.jeanlucmakiola.calendula.ui.common.startInstant
|
import de.jeanlucmakiola.calendula.ui.common.startInstant
|
||||||
@@ -519,13 +519,11 @@ private fun AllDayBar(
|
|||||||
.semantics { contentDescription = title },
|
.semantics { contentDescription = title },
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
) {
|
) {
|
||||||
val titleOverflow = eventTitleOverflow()
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = titleOverflow.overflow,
|
overflow = TextOverflow.Ellipsis,
|
||||||
softWrap = titleOverflow.softWrap,
|
|
||||||
color = eventInk(fill),
|
color = eventInk(fill),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -768,13 +766,11 @@ private fun EventBlock(
|
|||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
if (showTitle) {
|
if (showTitle) {
|
||||||
val titleOverflow = eventTitleOverflow(singleLine = showTime)
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
maxLines = if (showTime) 1 else 2,
|
maxLines = if (showTime) 1 else 2,
|
||||||
overflow = titleOverflow.overflow,
|
overflow = TextOverflow.Ellipsis,
|
||||||
softWrap = titleOverflow.softWrap,
|
|
||||||
color = eventInk(fill, alpha = 0.85f),
|
color = eventInk(fill, alpha = 0.85f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberDragSurface
|
import de.jeanlucmakiola.calendula.ui.common.rememberDragSurface
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventTitleOverflow
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.MoveTarget
|
import de.jeanlucmakiola.calendula.ui.common.MoveTarget
|
||||||
import de.jeanlucmakiola.calendula.ui.common.MoveRequest
|
import de.jeanlucmakiola.calendula.ui.common.MoveRequest
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
||||||
@@ -2353,13 +2352,16 @@ private fun MonthBar(
|
|||||||
},
|
},
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
) {
|
) {
|
||||||
val titleOverflow = eventTitleOverflow()
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = titleOverflow.overflow,
|
// No "…" (#164): the chip is narrow enough that the ellipsis costs a
|
||||||
softWrap = titleOverflow.softWrap,
|
// couple of readable characters. softWrap is off with it — left on,
|
||||||
|
// a clipped line breaks at the last whole word instead of running to
|
||||||
|
// the chip's edge, which reads as *less* title, not more.
|
||||||
|
overflow = TextOverflow.Clip,
|
||||||
|
softWrap = false,
|
||||||
color = eventInk(fill),
|
color = eventInk(fill),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ import androidx.compose.ui.semantics.contentDescription
|
|||||||
import androidx.compose.ui.semantics.customActions
|
import androidx.compose.ui.semantics.customActions
|
||||||
import androidx.compose.ui.semantics.semantics
|
import androidx.compose.ui.semantics.semantics
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -101,7 +102,6 @@ import de.jeanlucmakiola.calendula.ui.common.TimelineDrop
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
import de.jeanlucmakiola.calendula.ui.common.beginsOn
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventDragAllowed
|
import de.jeanlucmakiola.calendula.ui.common.eventDragAllowed
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
import de.jeanlucmakiola.calendula.ui.common.eventMoveAction
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventTitleOverflow
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
import de.jeanlucmakiola.calendula.ui.common.rememberEventDragSource
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
import de.jeanlucmakiola.calendula.ui.common.rememberTimelineDragController
|
||||||
import de.jeanlucmakiola.calendula.ui.common.startInstant
|
import de.jeanlucmakiola.calendula.ui.common.startInstant
|
||||||
@@ -656,13 +656,11 @@ private fun AllDayBar(
|
|||||||
.semantics { contentDescription = title },
|
.semantics { contentDescription = title },
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
) {
|
) {
|
||||||
val titleOverflow = eventTitleOverflow()
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = titleOverflow.overflow,
|
overflow = TextOverflow.Ellipsis,
|
||||||
softWrap = titleOverflow.softWrap,
|
|
||||||
color = eventInk(fill),
|
color = eventInk(fill),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -936,13 +934,11 @@ private fun EventBlock(
|
|||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
if (showTitle) {
|
if (showTitle) {
|
||||||
val titleOverflow = eventTitleOverflow(singleLine = titleMaxLines == 1)
|
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
maxLines = titleMaxLines,
|
maxLines = titleMaxLines,
|
||||||
overflow = titleOverflow.overflow,
|
overflow = TextOverflow.Ellipsis,
|
||||||
softWrap = titleOverflow.softWrap,
|
|
||||||
color = eventInk(fill, alpha = 0.85f),
|
color = eventInk(fill, alpha = 0.85f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.common
|
|
||||||
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
|
|
||||||
class EventTitleOverflowTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a single LTR line clips, so the title runs to the chip's edge`() {
|
|
||||||
val result = eventTitleOverflowFor(rtl = false, singleLine = true)
|
|
||||||
assertThat(result.overflow).isEqualTo(TextOverflow.Clip)
|
|
||||||
// Load-bearing: with softWrap on, a clipped line breaks at the last
|
|
||||||
// whole word and shows less title than the ellipsis did (#164).
|
|
||||||
assertThat(result.softWrap).isFalse()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `RTL keeps the ellipsis, which truncates at the logical end`() {
|
|
||||||
// Clipping with softWrap off cuts at the node's left edge, which in RTL
|
|
||||||
// is the end of the string — the title would lose its beginning.
|
|
||||||
val result = eventTitleOverflowFor(rtl = true, singleLine = true)
|
|
||||||
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
|
|
||||||
assertThat(result.softWrap).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a multi-line block keeps the ellipsis, since wrapping needs softWrap`() {
|
|
||||||
val result = eventTitleOverflowFor(rtl = false, singleLine = false)
|
|
||||||
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
|
|
||||||
assertThat(result.softWrap).isTrue()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `multi-line in RTL keeps the ellipsis too`() {
|
|
||||||
val result = eventTitleOverflowFor(rtl = true, singleLine = false)
|
|
||||||
assertThat(result.overflow).isEqualTo(TextOverflow.Ellipsis)
|
|
||||||
assertThat(result.softWrap).isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user