Match edge indicators to event colors and clear before reveal (#188)
This commit is contained in:
@@ -122,31 +122,3 @@ const val GREY_CHROMA = 0.02f
|
|||||||
/** Chroma band for harmonised colours: distinct, but never electric. */
|
/** Chroma band for harmonised colours: distinct, but never electric. */
|
||||||
const val MIN_CHROMA = 0.07f
|
const val MIN_CHROMA = 0.07f
|
||||||
const val MAX_CHROMA = 0.16f
|
const val MAX_CHROMA = 0.16f
|
||||||
|
|
||||||
/**
|
|
||||||
* Colour for the timeline's out-of-view edge bar (#188): the [eventTone]
|
|
||||||
* container's hue and chroma, stepped [EDGE_HINT_STEP] away from its lightness
|
|
||||||
* so the bar still reads apart from a block of the same calendar sitting right
|
|
||||||
* at the edge. It steps towards the surface's opposite — darker on a light
|
|
||||||
* scheme, lighter on a dark one — and only turns back when that runs out of
|
|
||||||
* room.
|
|
||||||
*/
|
|
||||||
fun edgeHintTone(rawArgb: Int, dark: Boolean, harmonise: Boolean): Int {
|
|
||||||
val container = oklchOf(eventTone(rawArgb, dark, harmonise).container)
|
|
||||||
val towardsContrast = if (dark) EDGE_HINT_STEP else -EDGE_HINT_STEP
|
|
||||||
val stepped = container.lightness + towardsContrast
|
|
||||||
val back = container.lightness - towardsContrast
|
|
||||||
val lightness = when {
|
|
||||||
stepped in EDGE_HINT_MIN_LIGHTNESS..EDGE_HINT_MAX_LIGHTNESS -> stepped
|
|
||||||
back in 0f..1f -> back
|
|
||||||
else -> stepped.coerceIn(0f, 1f)
|
|
||||||
}
|
|
||||||
return container.copy(lightness = lightness).toArgb()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** How far the edge bar's lightness sits from its event's container. */
|
|
||||||
const val EDGE_HINT_STEP = 0.16f
|
|
||||||
|
|
||||||
/** Lightness band the edge bar stays inside, clear of pure black and white. */
|
|
||||||
const val EDGE_HINT_MIN_LIGHTNESS = 0.22f
|
|
||||||
const val EDGE_HINT_MAX_LIGHTNESS = 0.94f
|
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package de.jeanlucmakiola.calendula.ui.common
|
package de.jeanlucmakiola.calendula.ui.common
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
|
||||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.ScrollState
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
@@ -14,8 +10,6 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
@@ -29,7 +23,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
@@ -40,7 +34,6 @@ import androidx.compose.ui.semantics.semantics
|
|||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import de.jeanlucmakiola.calendula.R
|
import de.jeanlucmakiola.calendula.R
|
||||||
import de.jeanlucmakiola.calendula.domain.color.edgeHintTone
|
|
||||||
import de.jeanlucmakiola.calendula.ui.week.TimedBlock
|
import de.jeanlucmakiola.calendula.ui.week.TimedBlock
|
||||||
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
import de.jeanlucmakiola.floret.identity.rememberReduceMotion
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -63,8 +56,8 @@ private val EDGE_TAP_REACH = 16.dp
|
|||||||
private val EDGE_BAR_VERTICAL_INSET = 4.dp
|
private val EDGE_BAR_VERTICAL_INSET = 4.dp
|
||||||
private val EDGE_BAR_HORIZONTAL_INSET = 6.dp
|
private val EDGE_BAR_HORIZONTAL_INSET = 6.dp
|
||||||
|
|
||||||
/** A block showing no more than this at the edge still counts as out of view. */
|
/** Distance outside the viewport over which a bar fades before its event appears. */
|
||||||
private val EDGE_SLACK = 12.dp
|
private val EDGE_FADE_LEAD = 24.dp
|
||||||
|
|
||||||
/** The nearest timed blocks wholly above and below a column's viewport. */
|
/** The nearest timed blocks wholly above and below a column's viewport. */
|
||||||
@Immutable
|
@Immutable
|
||||||
@@ -83,21 +76,19 @@ internal fun renderedEndMin(block: TimedBlock): Float =
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The blocks of one column lying out of a viewport spanning [topMin]..[bottomMin]
|
* The blocks of one column lying out of a viewport spanning [topMin]..[bottomMin]
|
||||||
* (minutes of the day), nearest first. A block that shows at most [slackMin] at
|
* (minutes of the day), nearest first. A block leaves this set as soon as any
|
||||||
* the edge counts as out of view, but only if some of it is actually hidden, so
|
* part of its rendered height enters the viewport.
|
||||||
* a column scrolled to its very top or bottom never points past it.
|
|
||||||
*/
|
*/
|
||||||
fun outOfViewEvents(
|
fun outOfViewEvents(
|
||||||
blocks: List<TimedBlock>,
|
blocks: List<TimedBlock>,
|
||||||
topMin: Float,
|
topMin: Float,
|
||||||
bottomMin: Float,
|
bottomMin: Float,
|
||||||
slackMin: Float,
|
|
||||||
): OutOfViewEvents {
|
): OutOfViewEvents {
|
||||||
val above = blocks
|
val above = blocks
|
||||||
.filter { it.startMin < topMin && renderedEndMin(it) <= topMin + slackMin }
|
.filter { renderedEndMin(it) <= topMin }
|
||||||
.maxWithOrNull(compareBy<TimedBlock>({ renderedEndMin(it) }, { it.startMin }))
|
.maxWithOrNull(compareBy<TimedBlock>({ renderedEndMin(it) }, { it.startMin }))
|
||||||
val below = blocks
|
val below = blocks
|
||||||
.filter { renderedEndMin(it) > bottomMin && it.startMin >= bottomMin - slackMin }
|
.filter { it.startMin >= bottomMin }
|
||||||
.minWithOrNull(compareBy<TimedBlock>({ it.startMin }, { -renderedEndMin(it) }))
|
.minWithOrNull(compareBy<TimedBlock>({ it.startMin }, { -renderedEndMin(it) }))
|
||||||
return OutOfViewEvents(above, below)
|
return OutOfViewEvents(above, below)
|
||||||
}
|
}
|
||||||
@@ -141,6 +132,13 @@ fun tappedEdge(
|
|||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Fade while the event is still outside; never draw its bar at the boundary. */
|
||||||
|
internal fun edgeBarAlpha(gapPx: Float, fadeLeadPx: Float): Float = when {
|
||||||
|
gapPx <= 0f -> 0f
|
||||||
|
fadeLeadPx <= 0f -> 1f
|
||||||
|
else -> (gapPx / fadeLeadPx).coerceIn(0f, 1f)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The out-of-view state of one day column (#188): which events hide past its
|
* The out-of-view state of one day column (#188): which events hide past its
|
||||||
* edges, and the scroll that reveals them.
|
* edges, and the scroll that reveals them.
|
||||||
@@ -155,7 +153,7 @@ class OutOfViewEdges internal constructor(
|
|||||||
) {
|
) {
|
||||||
internal var blocks by mutableStateOf(emptyList<TimedBlock>())
|
internal var blocks by mutableStateOf(emptyList<TimedBlock>())
|
||||||
internal var hourPx by mutableFloatStateOf(0f)
|
internal var hourPx by mutableFloatStateOf(0f)
|
||||||
internal var slackPx by mutableFloatStateOf(0f)
|
internal var fadeLeadPx by mutableFloatStateOf(0f)
|
||||||
internal var marginPx by mutableFloatStateOf(0f)
|
internal var marginPx by mutableFloatStateOf(0f)
|
||||||
internal var reachPx by mutableFloatStateOf(0f)
|
internal var reachPx by mutableFloatStateOf(0f)
|
||||||
internal var reduceMotion = false
|
internal var reduceMotion = false
|
||||||
@@ -171,7 +169,6 @@ class OutOfViewEdges internal constructor(
|
|||||||
blocks = blocks,
|
blocks = blocks,
|
||||||
topMin = top / hourPx * 60f,
|
topMin = top / hourPx * 60f,
|
||||||
bottomMin = (top + viewport) / hourPx * 60f,
|
bottomMin = (top + viewport) / hourPx * 60f,
|
||||||
slackMin = slackPx / hourPx * 60f,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,7 +223,7 @@ fun rememberOutOfViewEdges(
|
|||||||
SideEffect {
|
SideEffect {
|
||||||
edges.blocks = blocks
|
edges.blocks = blocks
|
||||||
edges.hourPx = hourPx
|
edges.hourPx = hourPx
|
||||||
edges.slackPx = with(density) { EDGE_SLACK.toPx() }
|
edges.fadeLeadPx = with(density) { EDGE_FADE_LEAD.toPx() }
|
||||||
edges.marginPx = with(density) { EDGE_TOUCH_HEIGHT.roundToPx().toFloat() }
|
edges.marginPx = with(density) { EDGE_TOUCH_HEIGHT.roundToPx().toFloat() }
|
||||||
edges.reachPx = with(density) { EDGE_TAP_REACH.toPx() }
|
edges.reachPx = with(density) { EDGE_TAP_REACH.toPx() }
|
||||||
edges.reduceMotion = reduceMotion
|
edges.reduceMotion = reduceMotion
|
||||||
@@ -241,7 +238,9 @@ fun rememberOutOfViewEdges(
|
|||||||
*
|
*
|
||||||
* Lives inside the column's scrolling content and follows the viewport by
|
* Lives inside the column's scrolling content and follows the viewport by
|
||||||
* offsetting against [scrollState] at placement rather than recomposing every
|
* offsetting against [scrollState] at placement rather than recomposing every
|
||||||
* frame. Taps reach it through [OutOfViewEdges.tap]; screen readers get a button.
|
* frame. Opacity follows the gap to the hidden event and reaches zero before
|
||||||
|
* the event enters view. Taps reach it through [OutOfViewEdges.tap]; screen
|
||||||
|
* readers get a button.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun BoxScope.OutOfViewEdgeBars(
|
fun BoxScope.OutOfViewEdgeBars(
|
||||||
@@ -256,6 +255,9 @@ fun BoxScope.OutOfViewEdgeBars(
|
|||||||
edge = ViewportEdge.Top,
|
edge = ViewportEdge.Top,
|
||||||
dark = dark,
|
dark = dark,
|
||||||
onReveal = edges::reveal,
|
onReveal = edges::reveal,
|
||||||
|
scrollState = scrollState,
|
||||||
|
hourPx = edges.hourPx,
|
||||||
|
fadeLeadPx = edges.fadeLeadPx,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
.offset { IntOffset(0, scrollState.value) },
|
.offset { IntOffset(0, scrollState.value) },
|
||||||
@@ -265,66 +267,62 @@ fun BoxScope.OutOfViewEdgeBars(
|
|||||||
edge = ViewportEdge.Bottom,
|
edge = ViewportEdge.Bottom,
|
||||||
dark = dark,
|
dark = dark,
|
||||||
onReveal = edges::reveal,
|
onReveal = edges::reveal,
|
||||||
|
scrollState = scrollState,
|
||||||
|
hourPx = edges.hourPx,
|
||||||
|
fadeLeadPx = edges.fadeLeadPx,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
.offset { IntOffset(0, scrollState.value + scrollState.viewportSize - touchPx) },
|
.offset { IntOffset(0, scrollState.value + scrollState.viewportSize - touchPx) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun EdgeBar(
|
private fun EdgeBar(
|
||||||
block: TimedBlock?,
|
block: TimedBlock?,
|
||||||
edge: ViewportEdge,
|
edge: ViewportEdge,
|
||||||
dark: Boolean,
|
dark: Boolean,
|
||||||
onReveal: (ViewportEdge) -> Unit,
|
onReveal: (ViewportEdge) -> Unit,
|
||||||
|
scrollState: ScrollState,
|
||||||
|
hourPx: Float,
|
||||||
|
fadeLeadPx: Float,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
// Held so the bar keeps its colour while it fades out.
|
block ?: return
|
||||||
val held = remember { mutableStateOf<TimedBlock?>(null) }
|
val color = eventFill(block.event.color, dark, LocalSoftenColors.current)
|
||||||
SideEffect { if (block != null) held.value = block }
|
|
||||||
val shown = block ?: held.value
|
|
||||||
val soften = LocalSoftenColors.current
|
|
||||||
val target = shown?.let { Color(edgeHintTone(it.event.color, dark, soften)) } ?: Color.Transparent
|
|
||||||
val effects = MaterialTheme.motionScheme.defaultEffectsSpec<Float>()
|
|
||||||
val color by animateColorAsState(
|
|
||||||
targetValue = target,
|
|
||||||
animationSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
|
|
||||||
label = "edge-bar-color",
|
|
||||||
)
|
|
||||||
val label = stringResource(
|
val label = stringResource(
|
||||||
if (edge == ViewportEdge.Top) R.string.timeline_earlier_events else R.string.timeline_later_events,
|
if (edge == ViewportEdge.Top) R.string.timeline_earlier_events else R.string.timeline_later_events,
|
||||||
)
|
)
|
||||||
AnimatedVisibility(
|
Box(
|
||||||
visible = block != null,
|
modifier = modifier
|
||||||
enter = fadeIn(effects),
|
.fillMaxWidth()
|
||||||
exit = fadeOut(effects),
|
.height(EDGE_TOUCH_HEIGHT)
|
||||||
modifier = modifier,
|
.graphicsLayer {
|
||||||
|
val gapPx = when (edge) {
|
||||||
|
ViewportEdge.Top -> scrollState.value - renderedEndMin(block) / 60f * hourPx
|
||||||
|
ViewportEdge.Bottom -> block.startMin / 60f * hourPx -
|
||||||
|
(scrollState.value + scrollState.viewportSize)
|
||||||
|
}
|
||||||
|
alpha = edgeBarAlpha(gapPx, fadeLeadPx)
|
||||||
|
}
|
||||||
|
.semantics {
|
||||||
|
role = Role.Button
|
||||||
|
contentDescription = label
|
||||||
|
onClick {
|
||||||
|
onReveal(edge)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = if (edge == ViewportEdge.Top) Alignment.TopCenter else Alignment.BottomCenter,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(EDGE_TOUCH_HEIGHT)
|
.padding(
|
||||||
.semantics {
|
horizontal = EDGE_BAR_HORIZONTAL_INSET,
|
||||||
role = Role.Button
|
vertical = EDGE_BAR_VERTICAL_INSET,
|
||||||
contentDescription = label
|
)
|
||||||
onClick {
|
.height(EDGE_BAR_THICKNESS)
|
||||||
onReveal(edge)
|
.background(color, CircleShape),
|
||||||
true
|
)
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = if (edge == ViewportEdge.Top) Alignment.TopCenter else Alignment.BottomCenter,
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(
|
|
||||||
horizontal = EDGE_BAR_HORIZONTAL_INSET,
|
|
||||||
vertical = EDGE_BAR_VERTICAL_INSET,
|
|
||||||
)
|
|
||||||
.height(EDGE_BAR_THICKNESS)
|
|
||||||
.background(color, CircleShape),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,37 +157,4 @@ class EventToneTest {
|
|||||||
.isEqualTo(0xFF000000.toInt())
|
.isEqualTo(0xFF000000.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `the edge bar steps clear of its block's container on every hue`() {
|
|
||||||
for (dark in listOf(false, true)) {
|
|
||||||
for (harmonise in listOf(false, true)) {
|
|
||||||
(hues + 0xFFFFF6C0.toInt() + 0xFF000000.toInt() + 0xFFFFFFFF.toInt()).forEach { raw ->
|
|
||||||
val container = oklchOf(eventTone(raw, dark, harmonise).container)
|
|
||||||
val bar = oklchOf(edgeHintTone(raw, dark, harmonise))
|
|
||||||
assertThat(kotlin.math.abs(bar.lightness - container.lightness))
|
|
||||||
.isAtLeast(EDGE_HINT_STEP - 0.02f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `the edge bar goes darker on a light scheme and lighter on a dark one`() {
|
|
||||||
hues.forEach { raw ->
|
|
||||||
val light = eventTone(raw, dark = false, harmonise = true).container
|
|
||||||
val dark = eventTone(raw, dark = true, harmonise = true).container
|
|
||||||
assertThat(oklchOf(edgeHintTone(raw, dark = false, harmonise = true)).lightness)
|
|
||||||
.isLessThan(oklchOf(light).lightness)
|
|
||||||
assertThat(oklchOf(edgeHintTone(raw, dark = true, harmonise = true)).lightness)
|
|
||||||
.isGreaterThan(oklchOf(dark).lightness)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a pale block on a dark scheme gets a deeper bar rather than a white one`() {
|
|
||||||
val pale = 0xFFFFF6C0.toInt()
|
|
||||||
val container = oklchOf(eventTone(pale, dark = true, harmonise = true).container)
|
|
||||||
assertThat(oklchOf(edgeHintTone(pale, dark = true, harmonise = true)).lightness)
|
|
||||||
.isLessThan(container.lightness)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class OutOfViewEdgesTest {
|
|||||||
private val late = block(5, 21 * 60, 22 * 60)
|
private val late = block(5, 21 * 60, 22 * 60)
|
||||||
|
|
||||||
// Viewport 10:00–17:00.
|
// Viewport 10:00–17:00.
|
||||||
private fun outOf(vararg blocks: TimedBlock, slackMin: Float = 0f) =
|
private fun outOf(vararg blocks: TimedBlock) =
|
||||||
outOfViewEvents(blocks.toList(), topMin = 600f, bottomMin = 1020f, slackMin = slackMin)
|
outOfViewEvents(blocks.toList(), topMin = 600f, bottomMin = 1020f)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `the nearest hidden event on each side is picked`() {
|
fun `the nearest hidden event on each side is picked`() {
|
||||||
@@ -56,19 +56,29 @@ class OutOfViewEdgesTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `a sliver within the slack still counts as out of view`() {
|
fun `an event disappears from the edge as soon as a sliver enters view`() {
|
||||||
val sliver = block(6, 9 * 60, 10 * 60 + 5)
|
val sliver = block(6, 9 * 60, 10 * 60 + 5)
|
||||||
assertThat(outOf(sliver).above).isNull()
|
assertThat(outOf(sliver).above).isNull()
|
||||||
assertThat(outOf(sliver, slackMin = 10f).above).isEqualTo(sliver)
|
|
||||||
val tip = block(7, 17 * 60 - 5, 18 * 60)
|
val tip = block(7, 17 * 60 - 5, 18 * 60)
|
||||||
assertThat(outOf(tip).below).isNull()
|
assertThat(outOf(tip).below).isNull()
|
||||||
assertThat(outOf(tip, slackMin = 10f).below).isEqualTo(tip)
|
val justAbove = block(8, 9 * 60, 10 * 60)
|
||||||
|
val justBelow = block(9, 17 * 60, 18 * 60)
|
||||||
|
assertThat(outOf(justAbove).above).isEqualTo(justAbove)
|
||||||
|
assertThat(outOf(justBelow).below).isEqualTo(justBelow)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `the bar fades before its event reaches the viewport`() {
|
||||||
|
assertThat(edgeBarAlpha(gapPx = 24f, fadeLeadPx = 24f)).isEqualTo(1f)
|
||||||
|
assertThat(edgeBarAlpha(gapPx = 12f, fadeLeadPx = 24f)).isEqualTo(0.5f)
|
||||||
|
assertThat(edgeBarAlpha(gapPx = 0f, fadeLeadPx = 24f)).isEqualTo(0f)
|
||||||
|
assertThat(edgeBarAlpha(gapPx = -1f, fadeLeadPx = 24f)).isEqualTo(0f)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `a column scrolled to its top points at nothing above it`() {
|
fun `a column scrolled to its top points at nothing above it`() {
|
||||||
val first = block(6, 0, 30)
|
val first = block(6, 0, 30)
|
||||||
val events = outOfViewEvents(listOf(first), topMin = 0f, bottomMin = 420f, slackMin = 30f)
|
val events = outOfViewEvents(listOf(first), topMin = 0f, bottomMin = 420f)
|
||||||
assertThat(events.above).isNull()
|
assertThat(events.above).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user