Wrap the dragged copy's time as the block does (#267)
The floating copy hardcoded maxLines = 1 on its time label and its title, so lifting a block whose text wrapped clipped it for as long as it was held and snapped back on drop. The copy now measures the range the same way the block does, and carries the block's title budget through the drag. Extracts that measurement into blockTimeLines, which Week and Day had a copy of each.
This commit is contained in:
@@ -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].
|
* A timed block's title, over at most [maxLines].
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ data class TimelineDrag(
|
|||||||
val eventSpanMin: Int,
|
val eventSpanMin: Int,
|
||||||
/** The event as the grid would draw it, one piece per day column it covers. */
|
/** The event as the grid would draw it, one piece per day column it covers. */
|
||||||
val pieces: List<TimelineDragPiece>,
|
val pieces: List<TimelineDragPiece>,
|
||||||
|
/**
|
||||||
|
* 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
|
* 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
|
* every column this timeline shows. A hint under the finger, not where the
|
||||||
@@ -311,6 +317,9 @@ class TimelineDragController {
|
|||||||
*/
|
*/
|
||||||
private var clipOffsetMin = 0
|
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
|
* 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).
|
* as its start, since the target snaps to the grid (09:07 lifts to 09:00).
|
||||||
@@ -320,11 +329,13 @@ class TimelineDragController {
|
|||||||
fun begin(
|
fun begin(
|
||||||
block: TimedBlock,
|
block: TimedBlock,
|
||||||
clipOffsetMin: Int,
|
clipOffsetMin: Int,
|
||||||
|
titleLines: Int,
|
||||||
pointerInRoot: Offset,
|
pointerInRoot: Offset,
|
||||||
blockInRoot: Offset,
|
blockInRoot: Offset,
|
||||||
) {
|
) {
|
||||||
source = block
|
source = block
|
||||||
this.clipOffsetMin = clipOffsetMin
|
this.clipOffsetMin = clipOffsetMin
|
||||||
|
this.titleLines = titleLines
|
||||||
settling = null
|
settling = null
|
||||||
isDragging = true
|
isDragging = true
|
||||||
liftedInstanceId = block.event.instanceId
|
liftedInstanceId = block.event.instanceId
|
||||||
@@ -343,6 +354,7 @@ class TimelineDragController {
|
|||||||
fun cancel() {
|
fun cancel() {
|
||||||
source = null
|
source = null
|
||||||
clipOffsetMin = 0
|
clipOffsetMin = 0
|
||||||
|
titleLines = 1
|
||||||
isDragging = false
|
isDragging = false
|
||||||
liftedInstanceId = null
|
liftedInstanceId = null
|
||||||
originSlot = null
|
originSlot = null
|
||||||
@@ -465,6 +477,7 @@ class TimelineDragController {
|
|||||||
startMin = startMin,
|
startMin = startMin,
|
||||||
eventStartMin = eventStartMin,
|
eventStartMin = eventStartMin,
|
||||||
eventSpanMin = eventSpan,
|
eventSpanMin = eventSpan,
|
||||||
|
titleLines = titleLines,
|
||||||
// Bounded to the columns this timeline actually shows: a day it
|
// Bounded to the columns this timeline actually shows: a day it
|
||||||
// doesn't has no piece to draw. The write is unaffected.
|
// doesn't has no piece to draw. The write is unaffected.
|
||||||
pieces = slices
|
pieces = slices
|
||||||
@@ -677,6 +690,7 @@ fun TimelineDragOverlay(controller: TimelineDragController, modifier: Modifier =
|
|||||||
lift = lift,
|
lift = lift,
|
||||||
alpha = copyAlpha,
|
alpha = copyAlpha,
|
||||||
title = title,
|
title = title,
|
||||||
|
titleLines = drag.titleLines,
|
||||||
// Once for the whole event: repeated, it would name it per day.
|
// Once for the whole event: repeated, it would name it per day.
|
||||||
label = label.takeIf { index == labelled },
|
label = label.takeIf { index == labelled },
|
||||||
)
|
)
|
||||||
@@ -696,9 +710,26 @@ private fun DragCopy(
|
|||||||
lift: Float,
|
lift: Float,
|
||||||
alpha: Float,
|
alpha: Float,
|
||||||
title: String,
|
title: String,
|
||||||
|
titleLines: Int,
|
||||||
label: String?,
|
label: String?,
|
||||||
) {
|
) {
|
||||||
val density = LocalDensity.current
|
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(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
// Absolute: these are root coordinates, and the direction-aware
|
// Absolute: these are root coordinates, and the direction-aware
|
||||||
@@ -709,10 +740,7 @@ private fun DragCopy(
|
|||||||
(topLeftInRoot.y - overlayOrigin.y).roundToInt(),
|
(topLeftInRoot.y - overlayOrigin.y).roundToInt(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.size(
|
.size(width = width, height = height)
|
||||||
width = with(density) { sizePx.width.toDp() },
|
|
||||||
height = with(density) { sizePx.height.toDp() },
|
|
||||||
)
|
|
||||||
.padding(horizontal = BLOCK_OUTER_INSET)
|
.padding(horizontal = BLOCK_OUTER_INSET)
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
scaleX = 1f + 0.02f * lift
|
scaleX = 1f + 0.02f * lift
|
||||||
@@ -726,25 +754,23 @@ private fun DragCopy(
|
|||||||
.padding(horizontal = BLOCK_TEXT_PADDING, vertical = 2.dp),
|
.padding(horizontal = BLOCK_TEXT_PADDING, vertical = 2.dp),
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
val titleOverflow = eventTitleOverflow()
|
BlockTitle(
|
||||||
Text(
|
title = title,
|
||||||
text = title,
|
maxLines = titleLines,
|
||||||
style = MaterialTheme.typography.labelMedium,
|
textWidth = textWidth,
|
||||||
maxLines = 1,
|
|
||||||
overflow = titleOverflow.overflow,
|
|
||||||
softWrap = titleOverflow.softWrap,
|
|
||||||
color = paint.titleInk,
|
color = paint.titleInk,
|
||||||
fontWeight = paint.titleWeight,
|
|
||||||
textDecoration = paint.decoration,
|
textDecoration = paint.decoration,
|
||||||
|
fontWeight = paint.titleWeight,
|
||||||
)
|
)
|
||||||
if (label != null) {
|
if (label != null) {
|
||||||
|
val overflow = eventTitleOverflow(singleLine = timeMaxLines == 1)
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
// As the block it lifted off sets its own time (#219).
|
// As the block it lifted off sets its own time (#219).
|
||||||
style = MaterialTheme.typography.labelSmall.asEventTime(),
|
style = MaterialTheme.typography.labelSmall.asEventTime(),
|
||||||
maxLines = 1,
|
maxLines = timeMaxLines,
|
||||||
overflow = titleOverflow.overflow,
|
overflow = overflow.overflow,
|
||||||
softWrap = titleOverflow.softWrap,
|
softWrap = overflow.softWrap,
|
||||||
color = paint.secondaryInk,
|
color = paint.secondaryInk,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.BLOCK_TEXT_PADDING
|
||||||
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
|
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.BlockTitle
|
import de.jeanlucmakiola.calendula.ui.common.BlockTitle
|
||||||
import de.jeanlucmakiola.calendula.ui.common.MAX_TIME_LINES
|
import de.jeanlucmakiola.calendula.ui.common.blockTimeLines
|
||||||
import de.jeanlucmakiola.calendula.ui.common.blockTextLines
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
|
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
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.LocalShowHourLines
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalTimelineZoom
|
import de.jeanlucmakiola.calendula.ui.common.LocalTimelineZoom
|
||||||
import de.jeanlucmakiola.calendula.ui.common.MIN_EVENT_FRACTION
|
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.hourHeight
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom
|
import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom
|
||||||
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
||||||
@@ -748,23 +746,11 @@ private fun EventBlock(
|
|||||||
val showTitle = available >= titleLineHeight
|
val showTitle = available >= titleLineHeight
|
||||||
val textWidth = width - (BLOCK_OUTER_INSET + BLOCK_TEXT_PADDING) * 2
|
val textWidth = width - (BLOCK_OUTER_INSET + BLOCK_TEXT_PADDING) * 2
|
||||||
val titleMaxLines = if (showTime) 1 else 2
|
val titleMaxLines = if (showTime) 1 else 2
|
||||||
// The range only wraps out of a line the title has not claimed, which on a
|
// On a day column — wide enough for "09:30–11:00" several times over — the
|
||||||
// day column — wide enough for "09:30–11:00" several times over — means it
|
// range never needs the second line, until lanes cut the column down.
|
||||||
// never does, until lanes cut the column down.
|
|
||||||
val spare = available - titleLineHeight * titleMaxLines -
|
val spare = available - titleLineHeight * titleMaxLines -
|
||||||
if (showTime) timeLineHeight else 0.dp
|
if (showTime) timeLineHeight else 0.dp
|
||||||
val timeMaxLines = if (showTime && spare >= timeLineHeight) {
|
val timeMaxLines = if (showTime) blockTimeLines(timeLabel, textWidth, spare) else 1
|
||||||
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 paint = eventPaint(block.event, dark)
|
val paint = eventPaint(block.event, dark)
|
||||||
val zone = remember { TimeZone.currentSystemDefault() }
|
val zone = remember { TimeZone.currentSystemDefault() }
|
||||||
val moveAction = eventMoveAction(block.event)
|
val moveAction = eventMoveAction(block.event)
|
||||||
@@ -780,7 +766,7 @@ private fun EventBlock(
|
|||||||
enabled = draggable,
|
enabled = draggable,
|
||||||
key = block.event.instanceId,
|
key = block.event.instanceId,
|
||||||
onPickUp = { pointer, blockRoot, _ ->
|
onPickUp = { pointer, blockRoot, _ ->
|
||||||
dragController.begin(block, clipOffset, pointer, blockRoot)
|
dragController.begin(block, clipOffset, titleMaxLines, pointer, blockRoot)
|
||||||
},
|
},
|
||||||
onMove = dragController::move,
|
onMove = dragController::move,
|
||||||
onDrop = { dragController.finish()?.let(onDrop) },
|
onDrop = { dragController.finish()?.let(onDrop) },
|
||||||
|
|||||||
@@ -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.BLOCK_TEXT_PADDING
|
||||||
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
|
import de.jeanlucmakiola.calendula.ui.common.BlockTimeLabel
|
||||||
import de.jeanlucmakiola.calendula.ui.common.BlockTitle
|
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.blockTextLines
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.blockTimeLines
|
||||||
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
|
import de.jeanlucmakiola.calendula.ui.common.animatedBlockPlacement
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
import de.jeanlucmakiola.calendula.ui.common.ghostAlpha
|
||||||
import de.jeanlucmakiola.calendula.ui.common.LocalEventMove
|
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.LocalTimelineZoom
|
||||||
import de.jeanlucmakiola.calendula.ui.common.MIN_EVENT_FRACTION
|
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.MIN_TITLE_WRAP_WIDTH
|
||||||
import de.jeanlucmakiola.calendula.ui.common.asEventTime
|
|
||||||
import de.jeanlucmakiola.calendula.ui.common.hourHeight
|
import de.jeanlucmakiola.calendula.ui.common.hourHeight
|
||||||
import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom
|
import de.jeanlucmakiola.calendula.ui.common.rememberTimelinePinchZoom
|
||||||
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
import de.jeanlucmakiola.calendula.ui.common.formatMinuteOfDay
|
||||||
@@ -918,23 +917,9 @@ private fun EventBlock(
|
|||||||
max = titleBudget,
|
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 -
|
val spare = available - titleLineHeight * titleMaxLines -
|
||||||
if (showTime) timeLineHeight else 0.dp
|
if (showTime) timeLineHeight else 0.dp
|
||||||
val timeMaxLines = if (showTime && spare >= timeLineHeight) {
|
val timeMaxLines = if (showTime) blockTimeLines(timeLabel, textWidth, spare) else 1
|
||||||
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 dimCutoff = LocalDimCutoff.current
|
val dimCutoff = LocalDimCutoff.current
|
||||||
val dimmed = dimCutoff != null && block.event.hasEnded(dimCutoff)
|
val dimmed = dimCutoff != null && block.event.hasEnded(dimCutoff)
|
||||||
val zone = remember { TimeZone.currentSystemDefault() }
|
val zone = remember { TimeZone.currentSystemDefault() }
|
||||||
@@ -951,7 +936,7 @@ private fun EventBlock(
|
|||||||
enabled = draggable,
|
enabled = draggable,
|
||||||
key = block.event.instanceId,
|
key = block.event.instanceId,
|
||||||
onPickUp = { pointer, blockRoot, _ ->
|
onPickUp = { pointer, blockRoot, _ ->
|
||||||
dragController.begin(block, clipOffset, pointer, blockRoot)
|
dragController.begin(block, clipOffset, titleMaxLines, pointer, blockRoot)
|
||||||
},
|
},
|
||||||
onMove = dragController::move,
|
onMove = dragController::move,
|
||||||
onDrop = { dragController.finish()?.let(onDrop) },
|
onDrop = { dragController.finish()?.let(onDrop) },
|
||||||
|
|||||||
Reference in New Issue
Block a user