fix(month): stop the row clip swallowing marks in mid-flight (#53)
Frame-by-frame, the marks were not fading in late — they were invisible for the first half of their journey and then appearing already near the destination, row by row from the top down. A mark's two homes are in different rows: the dot for the 20th sits a third of the way down the compact grid, its bar most of the way down the expanded one. The events box clips to its own row, so a mark arriving there is out of bounds — and undrawn — until it crosses in. The lower the row, the further it travels and the later it showed, which is exactly the order the capture shows them appearing in. Rendering in place rather than in an overlay is what subjects marks to that clip, and that is worth keeping — it is what puts the whole grid in one z-order. So the clip yields instead, and only while pieces are in flight; at rest it still stops a bar spilling into the row below. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,25 @@ internal class MonthMorphScope(
|
||||
|
||||
internal val LocalMonthMorph = compositionLocalOf<MonthMorphScope?> { null }
|
||||
|
||||
/**
|
||||
* True while the grid is mid-morph, for content that has to stop clipping to let
|
||||
* the travelling pieces through.
|
||||
*
|
||||
* A mark's two homes are in *different rows*: the dot for the 20th sits a third
|
||||
* of the way down the compact grid, its bar most of the way down the expanded
|
||||
* one. Clipped to the row it is arriving at, a mark spends the first half of its
|
||||
* journey outside those bounds and is simply not drawn — so it appears from
|
||||
* nowhere, part-way through, already near its destination. Rendering in place
|
||||
* rather than in an overlay is what subjects them to that clip, and is worth
|
||||
* keeping; the clip is what has to yield, and only while pieces are in flight.
|
||||
*/
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
internal fun morphInFlight(): Boolean {
|
||||
val morph = LocalMonthMorph.current ?: return false
|
||||
return morph.shared.isTransitionActive
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag content that is *the same thing* on both sides — a background pill, a day
|
||||
* number — so it animates between its two positions and sizes.
|
||||
|
||||
@@ -1647,6 +1647,7 @@ private fun MonthWeekRow(
|
||||
val dark = isSystemInDarkTheme()
|
||||
val laneCount = (week.spans.maxOfOrNull { it.lane } ?: -1) + 1
|
||||
val shownLanes = laneCount.coerceAtMost(MAX_EVENT_ROWS)
|
||||
val morphing = morphInFlight()
|
||||
|
||||
Row(modifier) {
|
||||
// Optional calendar-week gutter, sized so the seven day columns below
|
||||
@@ -1720,11 +1721,15 @@ private fun MonthWeekRow(
|
||||
// Breathing room between the day number (and today's circle) and the
|
||||
// first event row.
|
||||
Spacer(Modifier.height(DAY_NUMBER_GAP))
|
||||
// Clipped at rest so a bar can't spill into the row below, but not
|
||||
// while a morph is in flight: a mark travels between two different
|
||||
// rows, and clipping it to the one it is arriving at hides it for
|
||||
// the whole first half of the journey. See [morphInFlight].
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.clipToBounds(),
|
||||
.then(if (morphing) Modifier else Modifier.clipToBounds()),
|
||||
) {
|
||||
// Spanning bars on their shared lanes.
|
||||
week.spans.filter { it.lane < shownLanes }.forEach { span ->
|
||||
|
||||
Reference in New Issue
Block a user