Skip to content

Commit fcfc747

Browse files
committed
Remove the direction property from SceneScope
1 parent 68c41bd commit fcfc747

8 files changed

Lines changed: 98 additions & 42 deletions

File tree

examples/basic/src/commonMain/kotlin/AnimationScene.kt

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import androidx.compose.ui.unit.IntOffset
1111
import androidx.compose.ui.unit.dp
1212
import dev.bnorm.storyboard.StoryboardBuilder
1313
import dev.bnorm.storyboard.easel.template.*
14+
import dev.bnorm.storyboard.easel.template.rememberAdvanceDirection
1415
import dev.bnorm.storyboard.toState
1516
import kotlin.math.roundToInt
1617

@@ -19,8 +20,11 @@ fun StoryboardBuilder.AnimationScene() = scene(
1920
enterTransition = enter(end = SceneEnter(alignment = Alignment.CenterEnd)),
2021
exitTransition = exit(end = SceneExit(alignment = Alignment.CenterEnd)),
2122
) {
23+
fun <T> quick(): SpringSpec<T> = spring(stiffness = Spring.StiffnessVeryLow)
24+
2225
@OptIn(ExperimentalTransitionApi::class)
2326
val state = frame.createChildTransition { it.toState() }
27+
val direction = rememberAdvanceDirection()
2428

2529
Column(verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(16.dp)) {
2630
Header { Text("Animation") }
@@ -31,41 +35,35 @@ fun StoryboardBuilder.AnimationScene() = scene(
3135
val halfWidth = with(LocalDensity.current) { maxWidth.toPx().roundToInt() } / 2
3236
val halfHeight = with(LocalDensity.current) { maxWidth.toPx().roundToInt() } / 2
3337

38+
3439
state.AnimatedVisibility(
3540
visible = { it == 1 },
36-
enter = enter(
37-
start = { fadeIn(spring(stiffness = Spring.StiffnessVeryLow)) },
38-
end = { fadeIn() },
39-
),
41+
enter = direction.enter(start = { fadeIn(quick()) }, end = { fadeIn() }),
4042
exit = fadeOut(),
4143
modifier = Modifier.align(Alignment.Center),
4244
content = { Text("Things can appear!", style = MaterialTheme.typography.h4) },
4345
)
4446

47+
// TODO there seems to be a Compose bug where the first time it goes through the animation
48+
// it remembers it and won't change to the opposite direction.
49+
// - maybe not all of it though? only the direction it seems.
4550
state.AnimatedVisibility(
4651
visible = { it == 2 },
47-
enter = enter(
48-
start = {
49-
slideInHorizontally(
50-
spring(
51-
stiffness = Spring.StiffnessVeryLow,
52-
visibilityThreshold = IntOffset.VisibilityThreshold,
53-
)
54-
) { -halfWidth - it / 2 }
55-
},
52+
enter = fadeIn() + direction.enter(
53+
start = { slideInHorizontally(quick()) { -halfWidth - it / 2 } },
5654
end = { slideInHorizontally { halfWidth + it / 2 } },
57-
) + fadeIn(),
58-
exit = exit(
55+
),
56+
exit = fadeOut() + direction.exit(
5957
start = { slideOutHorizontally { -halfWidth - it / 2 } },
6058
end = { slideOutHorizontally { halfWidth + it / 2 } },
61-
) + fadeOut(),
59+
),
6260
modifier = Modifier.align(Alignment.Center),
6361
content = { Text("Things can move!", style = MaterialTheme.typography.h4) },
6462
)
6563

6664
state.AnimatedVisibility(
6765
visible = { it in 3..5 },
68-
enter = enter(
66+
enter = direction.enter(
6967
start = {
7068
slideInVertically(
7169
spring(

storyboard-easel/src/commonMain/kotlin/dev/bnorm/storyboard/easel/Preview.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ import kotlinx.collections.immutable.ImmutableList
2424
private class PreviewSceneScope<T>(
2525
override val states: ImmutableList<T>,
2626
override val frame: Transition<out Frame<T>>,
27-
) : SceneScope<T> {
28-
override val direction: AdvanceDirection get() = AdvanceDirection.Forward
29-
}
27+
) : SceneScope<T>
3028

3129
@Composable
3230
internal fun <T> ScenePreview(

storyboard-easel/src/commonMain/kotlin/dev/bnorm/storyboard/easel/StoryScene.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ fun StoryScene(storyState: StoryState, modifier: Modifier = Modifier) {
3333
) { scene ->
3434
holder.SaveableStateProvider(scene) {
3535
Box(Modifier.fillMaxSize()) {
36-
SceneContent(storyState, scene, stateFrame)
36+
SceneContent(scene, stateFrame)
3737
}
3838
}
3939
}
@@ -42,17 +42,13 @@ fun StoryScene(storyState: StoryState, modifier: Modifier = Modifier) {
4242
}
4343

4444
private class StoryboardSceneScope<T>(
45-
private val storyState: StoryState,
4645
override val states: ImmutableList<T>,
4746
override val frame: Transition<out Frame<T>>,
48-
) : SceneScope<T> {
49-
override val direction: AdvanceDirection get() = storyState.currentDirection
50-
}
47+
) : SceneScope<T>
5148

5249
@Composable
5350
context(_: AnimatedVisibilityScope, _: SharedTransitionScope)
5451
private fun <T> SceneContent(
55-
storyState: StoryState,
5652
scene: Scene<T>,
5753
stateFrame: Transition<StoryState.StateFrame<*>>,
5854
) {
@@ -65,9 +61,8 @@ private fun <T> SceneContent(
6561
}
6662
}
6763

68-
val scope = remember(storyState, scene, frame) {
64+
val scope = remember(scene, frame) {
6965
StoryboardSceneScope(
70-
storyState = storyState,
7166
states = scene.states,
7267
frame = frame,
7368
)

storyboard-easel/src/commonMain/kotlin/dev/bnorm/storyboard/easel/template/animation.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,6 @@ inline fun AdvanceDirection.exit(
5555
AdvanceDirection.Backward -> start()
5656
}
5757

58-
inline fun SceneScope<*>.enter(
59-
start: () -> EnterTransition = { EnterTransition.None },
60-
end: () -> EnterTransition = { EnterTransition.None },
61-
): EnterTransition = direction.enter(start, end)
62-
63-
inline fun SceneScope<*>.exit(
64-
start: () -> ExitTransition = { ExitTransition.None },
65-
end: () -> ExitTransition = { ExitTransition.None },
66-
): ExitTransition = direction.exit(start, end)
67-
6858
inline fun enter(
6959
crossinline start: (AdvanceDirection) -> EnterTransition = { EnterTransition.None },
7060
crossinline end: (AdvanceDirection) -> EnterTransition = { EnterTransition.None },
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dev.bnorm.storyboard.easel.template
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.remember
5+
import dev.bnorm.storyboard.AdvanceDirection
6+
import dev.bnorm.storyboard.Frame
7+
import dev.bnorm.storyboard.SceneScope
8+
import dev.bnorm.storyboard.compareTo
9+
10+
@Composable
11+
fun <T : Comparable<T>> SceneScope<T>.rememberAdvanceDirection(): AdvanceDirection {
12+
class Memory<R : Any>(initial: R) {
13+
private var memory = initial
14+
15+
fun fold(next: R?): R {
16+
memory = next ?: memory
17+
return memory
18+
}
19+
}
20+
21+
val memory = remember {
22+
Memory(
23+
when (frame.currentState) {
24+
is Frame.Start -> AdvanceDirection.Forward
25+
is Frame.End -> AdvanceDirection.Backward
26+
else -> AdvanceDirection.Forward
27+
}
28+
)
29+
}
30+
31+
return memory.fold(AdvanceDirection.from(frame.currentState, frame.targetState, Frame<T>::compareTo))
32+
}

storyboard/src/commonMain/kotlin/dev/bnorm/storyboard/AdvanceDirection.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,25 @@ package dev.bnorm.storyboard
33
enum class AdvanceDirection {
44
Forward,
55
Backward,
6+
;
7+
8+
companion object {
9+
fun <T : Comparable<T>> from(current: T, target: T): AdvanceDirection? {
10+
val compare = current.compareTo(target)
11+
return when {
12+
compare < 0 -> Forward
13+
compare > 0 -> Backward
14+
else -> null
15+
}
16+
}
17+
18+
fun <T> from(current: T, target: T, comparator: Comparator<T>): AdvanceDirection? {
19+
val compare = comparator.compare(current, target)
20+
return when {
21+
compare < 0 -> Forward
22+
compare > 0 -> Backward
23+
else -> null
24+
}
25+
}
26+
}
627
}

storyboard/src/commonMain/kotlin/dev/bnorm/storyboard/Frame.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,29 @@ sealed class Frame<out T> {
44
data object Start : Frame<Nothing>()
55
data object End : Frame<Nothing>()
66

7-
class State<out T>(val state: T) : Frame<T>()
7+
class State<out T>(val state: T) : Frame<T>() {
8+
override fun toString(): String = "State($state)"
9+
}
10+
}
11+
12+
operator fun <T : Comparable<T>> Frame<T>.compareTo(other: Frame<T>): Int {
13+
return when (this) {
14+
Frame.Start -> when (other) {
15+
Frame.Start -> 0
16+
else -> -1
17+
}
18+
19+
Frame.End -> when (other) {
20+
Frame.End -> 0
21+
else -> 1
22+
}
23+
24+
is Frame.State -> when (other) {
25+
Frame.Start -> 1
26+
Frame.End -> -1
27+
is Frame.State -> state.compareTo(other.state)
28+
}
29+
}
830
}
931

1032
fun <T, R> Frame<T>.map(transform: (T) -> R): Frame<R> {

storyboard/src/commonMain/kotlin/dev/bnorm/storyboard/SceneScope.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,16 @@ import kotlinx.collections.immutable.ImmutableList
99
interface SceneScope<T> {
1010
val states: ImmutableList<T>
1111
val frame: Transition<out Frame<T>>
12-
13-
// TODO figure out how to remove this from the scope...
14-
val direction: AdvanceDirection
1512
}
1613

14+
// TODO do we want this?
1715
val <T> SceneScope<T>.currentState: T
1816
get() {
1917
require(states.isNotEmpty()) { "implicit conversion to state requires non-empty states" }
2018
return frame.currentState.toState()
2119
}
2220

21+
// TODO where's the best place for this?
2322
context(sceneScope: SceneScope<T>)
2423
fun <T, R : T> Frame<R>.toState(): T {
2524
require(sceneScope.states.isNotEmpty()) { "implicit conversion to state requires non-empty states" }
@@ -29,3 +28,4 @@ fun <T, R : T> Frame<R>.toState(): T {
2928
is Frame.State -> state
3029
}
3130
}
31+

0 commit comments

Comments
 (0)