diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/BlockPlacement.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/BlockPlacement.kt index 5ddc2b3..7e34651 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/BlockPlacement.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/BlockPlacement.kt @@ -61,6 +61,33 @@ fun blockTextLines(text: String, style: TextStyle, textWidth: Dp, max: Int): Int } } +/** + * Lines the time label may take at [textWidth], out of the [spare] height left + * once the title and the label's own first line are paid for. + * + * A week column is narrower than a "09:30–11:00" range, so the label takes a + * second line rather than lose its end — but only out of a line the title + * measured itself as not needing, never one it would have filled. + */ +@Composable +fun blockTimeLines(label: String, textWidth: Dp, spare: Dp): Int { + val timeLineHeight = with(LocalDensity.current) { + MaterialTheme.typography.labelSmall.lineHeight.toDp() + } + return if (spare >= timeLineHeight) { + blockTextLines( + text = label, + // The style it is drawn in, or the budget measures a line the label + // never uses (#219). + style = MaterialTheme.typography.labelSmall.asEventTime(), + textWidth = textWidth, + max = MAX_TIME_LINES, + ) + } else { + 1 + } +} + /** * A timed block's title, over at most [maxLines]. * diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt index 5c79f3a..d9ee0b7 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/common/TimelineDrag.kt @@ -81,6 +81,12 @@ data class TimelineDrag( val eventSpanMin: Int, /** The event as the grid would draw it, one piece per day column it covers. */ val pieces: List, + /** + * Lines the block the finger picked up gave its title. The floating copy is + * that block's size, so it has to spend its height the same way or the text + * re-wraps under the finger and snaps back on drop (#267). + */ + val titleLines: Int, /** * True when [pieces] is only [edgeDragSlice]'s stand-in — the event has left * every column this timeline shows. A hint under the finger, not where the @@ -311,6 +317,9 @@ class TimelineDragController { */ private var clipOffsetMin = 0 + /** What the picked-up block measured its title over — see [TimelineDrag.titleLines]. */ + private var titleLines = 1 + /** * The slot the block already occupied when it was picked up — not the same * as its start, since the target snaps to the grid (09:07 lifts to 09:00). @@ -320,11 +329,13 @@ class TimelineDragController { fun begin( block: TimedBlock, clipOffsetMin: Int, + titleLines: Int, pointerInRoot: Offset, blockInRoot: Offset, ) { source = block this.clipOffsetMin = clipOffsetMin + this.titleLines = titleLines settling = null isDragging = true liftedInstanceId = block.event.instanceId @@ -343,6 +354,7 @@ class TimelineDragController { fun cancel() { source = null clipOffsetMin = 0 + titleLines = 1 isDragging = false liftedInstanceId = null originSlot = null @@ -465,6 +477,7 @@ class TimelineDragController { startMin = startMin, eventStartMin = eventStartMin, eventSpanMin = eventSpan, + titleLines = titleLines, // Bounded to the columns this timeline actually shows: a day it // doesn't has no piece to draw. The write is unaffected. pieces = slices @@ -677,6 +690,7 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier = lift = lift, alpha = copyAlpha, title = title, + titleLines = drag.titleLines, // Once for the whole event: repeated, it would name it per day. label = label.takeIf { index == labelled }, ) @@ -696,9 +710,26 @@ private fun DragCopy( lift: Float, alpha: Float, title: String, + titleLines: Int, label: String?, ) { val density = LocalDensity.current + val width = with(density) { sizePx.width.toDp() } + val height = with(density) { sizePx.height.toDp() } + val textWidth = width - (BLOCK_OUTER_INSET + BLOCK_TEXT_PADDING) * 2 + // The copy is the block's size and spends its height the same way, so text + // wraps here exactly where it wrapped there. Held to one line the range + // clipped for the drag's duration and snapped back on drop (#267). The one + // thing the copy does that a short block won't is always show the range — + // that is the feedback the drag is for. + val titleLineHeight = with(density) { + MaterialTheme.typography.labelMedium.lineHeight.toDp() + } + val timeLineHeight = with(density) { + MaterialTheme.typography.labelSmall.lineHeight.toDp() + } + val spare = height - 4.dp - titleLineHeight * titleLines - timeLineHeight + val timeMaxLines = if (label == null) 1 else blockTimeLines(label, textWidth, spare) Box( modifier = Modifier // Absolute: these are root coordinates, and the direction-aware @@ -709,10 +740,7 @@ private fun DragCopy( (topLeftInRoot.y - overlayOrigin.y).roundToInt(), ) } - .size( - width = with(density) { sizePx.width.toDp() }, - height = with(density) { sizePx.height.toDp() }, - ) + .size(width = width, height = height) .padding(horizontal = BLOCK_OUTER_INSET) .graphicsLayer { scaleX = 1f + 0.02f * lift @@ -726,25 +754,23 @@ private fun DragCopy( .padding(horizontal = BLOCK_TEXT_PADDING, vertical = 2.dp), ) { Column { - val titleOverflow = eventTitleOverflow() - Text( - text = title, - style = MaterialTheme.typography.labelMedium, - maxLines = 1, - overflow = titleOverflow.overflow, - softWrap = titleOverflow.softWrap, + BlockTitle( + title = title, + maxLines = titleLines, + textWidth = textWidth, color = paint.titleInk, - fontWeight = paint.titleWeight, textDecoration = paint.decoration, + fontWeight = paint.titleWeight, ) if (label != null) { + val overflow = eventTitleOverflow(singleLine = timeMaxLines == 1) Text( text = label, // As the block it lifted off sets its own time (#219). style = MaterialTheme.typography.labelSmall.asEventTime(), - maxLines = 1, - overflow = titleOverflow.overflow, - softWrap = titleOverflow.softWrap, + maxLines = timeMaxLines, + overflow = overflow.overflow, + softWrap = overflow.softWrap, color = paint.secondaryInk, ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt index 53bd0a4..ed1b713 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/day/DayScreen.kt @@ -85,8 +85,7 @@ import de.jeanlucmakiola.calendula.ui.common.BLOCK_OUTER_INSET import de.jeanlucmakiola.calendula.ui.common.BLOCK_TEXT_PADDING import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel import de.jeanlucmakiola.calendula.ui.common.BlockTitle -import de.jeanlucmakiola.calendula.ui.common.MAX_TIME_LINES -import de.jeanlucmakiola.calendula.ui.common.blockTextLines +import de.jeanlucmakiola.calendula.ui.common.blockTimeLines import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement import de.jeanlucmakiola.calendula.ui.common.ghostAlpha import de.jeanlucmakiola.calendula.ui.common.LocalEventMove @@ -120,7 +119,6 @@ import de.jeanlucmakiola.calendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.calendula.ui.common.LocalShowHourLines import de.jeanlucmakiola.calendula.ui.common.LocalTimelineZoom import de.jeanlucmakiola.calendula.ui.common.MIN_EVENT_FRACTION -import de.jeanlucmakiola.calendula.ui.common.asEventTime import de.jeanlucmakiola.calendula.ui.common.hourHeight import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay @@ -748,23 +746,11 @@ private fun EventBlock( val showTitle = available >= titleLineHeight val textWidth = width - (BLOCK_OUTER_INSET + BLOCK_TEXT_PADDING) * 2 val titleMaxLines = if (showTime) 1 else 2 - // The range only wraps out of a line the title has not claimed, which on a - // day column — wide enough for "09:30–11:00" several times over — means it - // never does, until lanes cut the column down. + // On a day column — wide enough for "09:30–11:00" several times over — the + // range never needs the second line, until lanes cut the column down. val spare = available - titleLineHeight * titleMaxLines - if (showTime) timeLineHeight else 0.dp - val timeMaxLines = if (showTime && spare >= timeLineHeight) { - blockTextLines( - text = timeLabel, - // The style it is drawn in, or the budget measures a line the label - // never uses (#219). - style = MaterialTheme.typography.labelSmall.asEventTime(), - textWidth = textWidth, - max = MAX_TIME_LINES, - ) - } else { - 1 - } + val timeMaxLines = if (showTime) blockTimeLines(timeLabel, textWidth, spare) else 1 val paint = eventPaint(block.event, dark) val zone = remember { TimeZone.currentSystemDefault() } val moveAction = eventMoveAction(block.event) @@ -780,7 +766,7 @@ private fun EventBlock( enabled = draggable, key = block.event.instanceId, onPickUp = { pointer, blockRoot, _ -> - dragController.begin(block, clipOffset, pointer, blockRoot) + dragController.begin(block, clipOffset, titleMaxLines, pointer, blockRoot) }, onMove = dragController::move, onDrop = { dragController.finish()?.let(onDrop) }, diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt index 740dd7f..3d97b23 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/week/WeekScreen.kt @@ -93,8 +93,8 @@ import de.jeanlucmakiola.calendula.ui.common.BLOCK_OUTER_INSET import de.jeanlucmakiola.calendula.ui.common.BLOCK_TEXT_PADDING import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel import de.jeanlucmakiola.calendula.ui.common.BlockTitle -import de.jeanlucmakiola.calendula.ui.common.MAX_TIME_LINES import de.jeanlucmakiola.calendula.ui.common.blockTextLines +import de.jeanlucmakiola.calendula.ui.common.blockTimeLines import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement import de.jeanlucmakiola.calendula.ui.common.ghostAlpha import de.jeanlucmakiola.calendula.ui.common.LocalEventMove @@ -130,7 +130,6 @@ import de.jeanlucmakiola.calendula.ui.common.LocalShowHourLines import de.jeanlucmakiola.calendula.ui.common.LocalTimelineZoom import de.jeanlucmakiola.calendula.ui.common.MIN_EVENT_FRACTION import de.jeanlucmakiola.calendula.ui.common.MIN_TITLE_WRAP_WIDTH -import de.jeanlucmakiola.calendula.ui.common.asEventTime import de.jeanlucmakiola.calendula.ui.common.hourHeight import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay @@ -918,23 +917,9 @@ private fun EventBlock( max = titleBudget, ) } - // A week column is narrower than a "09:30–11:00" range, so the label takes a - // second line rather than lose its end — but only out of a line the title - // measured itself as not needing, never one it would have filled. val spare = available - titleLineHeight * titleMaxLines - if (showTime) timeLineHeight else 0.dp - val timeMaxLines = if (showTime && spare >= timeLineHeight) { - blockTextLines( - text = timeLabel, - // The style it is drawn in, or the budget measures a line the label - // never uses (#219). - style = MaterialTheme.typography.labelSmall.asEventTime(), - textWidth = textWidth, - max = MAX_TIME_LINES, - ) - } else { - 1 - } + val timeMaxLines = if (showTime) blockTimeLines(timeLabel, textWidth, spare) else 1 val dimCutoff = LocalDimCutoff.current val dimmed = dimCutoff != null && block.event.hasEnded(dimCutoff) val zone = remember { TimeZone.currentSystemDefault() } @@ -951,7 +936,7 @@ private fun EventBlock( enabled = draggable, key = block.event.instanceId, onPickUp = { pointer, blockRoot, _ -> - dragController.begin(block, clipOffset, pointer, blockRoot) + dragController.begin(block, clipOffset, titleMaxLines, pointer, blockRoot) }, onMove = dragController::move, onDrop = { dragController.finish()?.let(onDrop) },