Skip to content

Commit c559e5f

Browse files
committed
More overview cleanup
1 parent c222ffe commit c559e5f

8 files changed

Lines changed: 63 additions & 95 deletions

File tree

storyboard-core/src/commonMain/kotlin/dev/bnorm/storyboard/core/StoryState.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@ import androidx.compose.animation.core.SeekableTransitionState
44
import androidx.compose.animation.core.Transition
55
import androidx.compose.animation.core.createChildTransition
66
import androidx.compose.animation.core.rememberTransition
7-
import androidx.compose.runtime.Composable
8-
import androidx.compose.runtime.getValue
9-
import androidx.compose.runtime.mutableStateOf
7+
import androidx.compose.runtime.*
108
import androidx.compose.runtime.saveable.rememberSaveable
11-
import androidx.compose.runtime.setValue
129
import androidx.compose.runtime.snapshots.Snapshot
1310
import kotlin.math.abs
1411

@@ -34,6 +31,7 @@ fun rememberStoryState(
3431
return rememberSaveable { StoryState(initialIndex) }
3532
}
3633

34+
@Stable
3735
class StoryState @ExperimentalStoryStateApi constructor(
3836
initialIndex: Storyboard.Index,
3937
) {

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import kotlinx.coroutines.launch
2424
@Composable
2525
fun Story(
2626
storyState: StoryState,
27-
storyOverviewState: StoryOverviewState = remember(storyState.storyboard) { StoryOverviewState.of(storyState) },
2827
overlay: @Composable StoryOverlayScope.() -> Unit = {},
2928
modifier: Modifier = Modifier,
3029
) {
@@ -33,9 +32,14 @@ fun Story(
3332

3433
val holder = rememberSaveableStateHolder()
3534

35+
val storyboard = storyState.storyboard
36+
val storyOverviewState = remember(storyboard) { StoryOverviewState.of(storyboard) }
37+
var overviewVisible by remember { mutableStateOf(false) } // TODO support initial visibility?
38+
3639
fun handleKeyEvent(event: KeyEvent): Boolean {
3740
if (event.type == KeyEventType.KeyUp && event.key == Key.Escape) {
38-
storyOverviewState.isVisible = true
41+
storyOverviewState.jumpToIndex(storyState.currentIndex)
42+
overviewVisible = true
3943
return true
4044
}
4145
return false
@@ -44,25 +48,26 @@ fun Story(
4448
Box(contentAlignment = Alignment.Center, modifier = modifier) {
4549
SharedTransitionLayout {
4650
AnimatedContent(
47-
targetState = storyOverviewState.isVisible,
51+
targetState = overviewVisible,
4852
transitionSpec = { fadeIn(tween(300)) togetherWith fadeOut(tween(300)) }
4953
) { isOverview ->
5054
if (isOverview) {
5155
StoryOverview(
52-
overview = storyOverviewState,
56+
storyState = storyState,
57+
storyOverviewState = storyOverviewState,
5358
onExitOverview = {
5459
job?.cancel()
5560
job = coroutineScope.launch {
5661
storyState.jumpTo(it)
5762
job = null
58-
storyOverviewState.isVisible = false
63+
overviewVisible = false
5964
}
6065
},
6166
modifier = Modifier.fillMaxSize(),
6267
)
6368
} else {
64-
holder.SaveableStateProvider(storyState.storyboard) {
65-
Box(contentAlignment = Alignment.Center, modifier = modifier.fillMaxSize()) {
69+
holder.SaveableStateProvider(storyboard) {
70+
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
6671
StoryOverlay(overlay = overlay) {
6772
StoryScene(
6873
storyState = storyState,

storyboard-easel/src/commonMain/kotlin/dev/bnorm/storyboard/easel/internal/pixels.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.

storyboard-easel/src/commonMain/kotlin/dev/bnorm/storyboard/easel/notes/ShowAssist.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon
1313
import androidx.compose.ui.unit.dp
1414
import dev.bnorm.storyboard.core.Storyboard
1515
import dev.bnorm.storyboard.core.StoryState
16-
import dev.bnorm.storyboard.easel.internal.aspectRatio
1716
import dev.bnorm.storyboard.easel.internal.requestFocus
1817
import dev.bnorm.storyboard.easel.onStoryboardNavigation
1918
import dev.bnorm.storyboard.ui.ScenePreview
@@ -89,7 +88,7 @@ private fun ClickableScenePreview(
8988
var job by remember { mutableStateOf<Job?>(null) }
9089

9190
// TODO share with StoryboardOverview?
92-
Box(modifier.aspectRatio(storyboard.storyboard.size.aspectRatio)) {
91+
Box(modifier) {
9392
ScenePreview(
9493
storyboard = storyboard.storyboard,
9594
index = index,

storyboard-easel/src/commonMain/kotlin/dev/bnorm/storyboard/easel/overview/StoryOverview.kt

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,56 +24,63 @@ import androidx.compose.ui.input.pointer.pointerHoverIcon
2424
import androidx.compose.ui.platform.LocalDensity
2525
import androidx.compose.ui.unit.*
2626
import dev.bnorm.storyboard.core.Frame
27+
import dev.bnorm.storyboard.core.StoryState
2728
import dev.bnorm.storyboard.core.Storyboard
28-
import dev.bnorm.storyboard.easel.internal.aspectRatio
2929
import dev.bnorm.storyboard.easel.internal.requestFocus
3030
import dev.bnorm.storyboard.easel.rememberSharedContentState
3131
import dev.bnorm.storyboard.easel.sharedElement
3232
import dev.bnorm.storyboard.ui.ScenePreview
3333

34+
// TODO keep assistant up-to-date?
35+
// TODO disable assistant navigation while in overview?
3436
@Composable
3537
context(_: AnimatedVisibilityScope, _: SharedTransitionScope)
3638
fun StoryOverview(
37-
overview: StoryOverviewState,
39+
storyState: StoryState,
40+
storyOverviewState: StoryOverviewState,
3841
onExitOverview: (Storyboard.Index) -> Unit = {},
3942
modifier: Modifier = Modifier,
4043
) {
41-
BoxWithConstraints(modifier = modifier.onOverviewNavigation(overview, onExitOverview)) {
44+
fun onClick(columnIndex: Int, itemIndex: Int, item: StoryOverviewState.StateItem<*>) {
45+
if (item == storyOverviewState.currentItem) {
46+
onExitOverview(item.index)
47+
}
48+
storyOverviewState.jumpTo(columnIndex, itemIndex)
49+
}
50+
51+
BoxWithConstraints(modifier = modifier.onOverviewNavigation(storyOverviewState, onExitOverview)) {
4252
val viewSize = DpSize(maxWidth, maxHeight)
4353
val itemSize = calculateItemSize(
4454
viewSize = viewSize,
45-
itemSize = overview.storyState.storyboard.size
55+
itemSize = storyState.storyboard.size
4656
)
4757

48-
val hState = rememberOverviewSceneScroll(viewSize, itemSize, overview)
58+
val hState = rememberOverviewSceneScroll(viewSize, itemSize, storyOverviewState)
4959

5060
LazyRow(
5161
state = hState,
5262
modifier = Modifier.fillMaxSize(),
5363
) {
54-
itemsIndexed(overview.columns) { columnIndex, column ->
55-
val isCurrentColumn = columnIndex == overview.currentColumnIndex
64+
itemsIndexed(storyOverviewState.columns) { columnIndex, column ->
65+
val isCurrentColumn = columnIndex == storyOverviewState.currentColumnIndex
5666
val verticalPaddingDp = (maxHeight - itemSize.height).coerceAtLeast(0.dp) / 2
5767
val vState = rememberOverviewStateScroll(itemSize, column)
5868

5969
// TODO how to keep the user from scrolling to a start or end frame?
6070
// - invisible padding item with different content padding?
6171
OverviewLazyColumn(
6272
state = vState,
63-
storyboard = overview.storyState.storyboard,
73+
storyboard = storyState.storyboard,
6474
column = column,
6575
verticalPaddingDp = verticalPaddingDp,
6676
itemSize = itemSize,
6777
isCurrentColumn = isCurrentColumn,
68-
onClick = { index, item ->
69-
val isCurrentIndex = isCurrentColumn && column.currentItemIndex == index
70-
if (isCurrentIndex) {
71-
onExitOverview(item.index)
72-
} else {
73-
if (column.items.isNotEmpty()) {
74-
overview.jumpTo(columnIndex, index)
75-
}
76-
}
78+
onClick = { itemIndex, item ->
79+
onClick(
80+
columnIndex = columnIndex,
81+
itemIndex = itemIndex,
82+
item = item
83+
)
7784
},
7885
)
7986
}
@@ -234,7 +241,7 @@ private fun calculateItemSize(
234241
min: Dp = 256.dp,
235242
max: Dp = 512.dp,
236243
): DpSize {
237-
val aspectRatio = itemSize.aspectRatio
244+
val aspectRatio = itemSize.width / itemSize.height
238245

239246
fun fromWidth(itemWidth: Dp): DpSize = DpSize(
240247
height = itemPadding + itemWidth / aspectRatio,
Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,31 @@
11
package dev.bnorm.storyboard.easel.overview
22

3-
import androidx.compose.runtime.Immutable
4-
import androidx.compose.runtime.getValue
5-
import androidx.compose.runtime.mutableStateOf
6-
import androidx.compose.runtime.setValue
3+
import androidx.compose.runtime.*
74
import dev.bnorm.storyboard.core.Scene
8-
import dev.bnorm.storyboard.core.StoryState
95
import dev.bnorm.storyboard.core.Storyboard
106
import kotlinx.collections.immutable.ImmutableList
117
import kotlinx.collections.immutable.toImmutableList
128

13-
class StoryOverviewState private constructor(
14-
val storyState: StoryState,
15-
internal val columns: ImmutableList<SceneColumn<*>>,
9+
@Stable
10+
class StoryOverviewState internal constructor(
11+
internal val columns: List<SceneColumn<*>>,
1612
) {
17-
private var _isVisible = mutableStateOf(false)
18-
var isVisible: Boolean
19-
get() = _isVisible.value
20-
set(value) {
21-
// If the overview becomes visible, make sure it is on the correct frame.
22-
if (value == true) jumpToFrame()
23-
_isVisible.value = value
24-
}
25-
2613
internal var currentColumnIndex by mutableStateOf(0)
2714
internal val currentItem: StateItem<*>
2815
get() = columns[currentColumnIndex].let { it.items[it.currentItemIndex] }
2916

17+
fun jumpToIndex(index: Storyboard.Index) {
18+
// TODO this can probably be optimized since we don't hide scenes anymore...
19+
currentColumnIndex =
20+
columns.binarySearch { compareValues(it.index, index.sceneIndex) }
21+
.coerceAtLeast(0)
22+
23+
val column = columns[currentColumnIndex]
24+
column.currentItemIndex =
25+
column.items.binarySearch { compareValues(it.index.stateIndex, index.stateIndex) }
26+
.coerceAtLeast(0)
27+
}
28+
3029
internal fun jumpTo(columnIndex: Int, itemIndex: Int) {
3130
val coercedColumnIndex = columnIndex.coerceIn(columns.indices)
3231
currentColumnIndex = coercedColumnIndex
@@ -39,19 +38,6 @@ class StoryOverviewState private constructor(
3938
currentColumnIndex = coercedColumnIndex
4039
}
4140

42-
private fun jumpToFrame() {
43-
val currentFrame = storyState.currentIndex
44-
45-
currentColumnIndex =
46-
columns.binarySearch { compareValues(it.index, currentFrame.sceneIndex) }
47-
.coerceAtLeast(0)
48-
49-
val column = columns[currentColumnIndex]
50-
column.currentItemIndex =
51-
column.items.binarySearch { compareValues(it.index.stateIndex, currentFrame.stateIndex) }
52-
.coerceAtLeast(0)
53-
}
54-
5541
@Immutable
5642
internal class SceneColumn<T>(
5743
val scene: Scene<*>,
@@ -71,8 +57,8 @@ class StoryOverviewState private constructor(
7157
)
7258

7359
companion object {
74-
fun of(storyState: StoryState): StoryOverviewState {
75-
val lastSceneIndex = storyState.storyboard.scenes.lastIndex
60+
internal fun of(storyboard: Storyboard): StoryOverviewState {
61+
val lastSceneIndex = storyboard.scenes.lastIndex
7662

7763
fun <T> SceneColumn(scene: Scene<T>, sceneIndex: Int): SceneColumn<T> {
7864
return SceneColumn(
@@ -89,14 +75,13 @@ class StoryOverviewState private constructor(
8975
)
9076
}
9177

92-
val columns = storyState.storyboard.scenes
78+
val columns = storyboard.scenes
9379
.mapIndexed { sceneIndex, scene -> SceneColumn(scene, sceneIndex) }
9480
.toImmutableList()
9581

9682
return StoryOverviewState(
97-
storyState = storyState,
9883
columns = columns,
9984
)
10085
}
10186
}
102-
}
87+
}

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

File renamed without changes.

storyboard-easel/src/wasmJsMain/kotlin/dev/bnorm/storyboard/easel/WebStory.kt

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import dev.bnorm.storyboard.core.StoryState
1212
import dev.bnorm.storyboard.core.Storyboard
1313
import dev.bnorm.storyboard.easel.overlay.OverlayNavigation
1414
import dev.bnorm.storyboard.easel.overlay.StoryOverlayScope
15-
import dev.bnorm.storyboard.easel.overview.StoryOverviewState
1615
import kotlinx.browser.window
1716
import org.w3c.dom.url.URL
1817
import org.w3c.dom.url.URLSearchParams
@@ -48,22 +47,10 @@ fun WebStory(
4847
OverlayNavigation(storyState)
4948
},
5049
) {
51-
val overview = remember(storyState.storyboard) { StoryOverviewState.of(storyState) }
52-
53-
// TODO should we be exposing overview as a param?
54-
// - we don't support this on desktop...
55-
remember {
56-
val params = URLSearchParams(window.location.search.toJsString())
57-
if (params.get("overview").toBoolean()) {
58-
overview.isVisible = true
59-
}
60-
}
61-
62-
LaunchedWindowHistoryUpdate(storyState, overview)
50+
LaunchedWindowHistoryUpdate(storyState)
6351

6452
Story(
6553
storyState = storyState,
66-
storyOverviewState = overview,
6754
overlay = {
6855
// TODO if this is a mobile device, prefer touch navigation
6956
overlay()
@@ -74,25 +61,18 @@ fun WebStory(
7461
}
7562

7663
@Composable
77-
private fun LaunchedWindowHistoryUpdate(storyState: StoryState, storyOverviewState: StoryOverviewState) {
64+
private fun LaunchedWindowHistoryUpdate(storyState: StoryState) {
7865
val frame = storyState.currentIndex
79-
val overviewVisible = storyOverviewState.isVisible
8066
// TODO LaunchedEffect?
8167
// - maybe remember would be better since this more of a side effect?
82-
LaunchedEffect(frame, overviewVisible) {
68+
LaunchedEffect(frame) {
8369
val url = URL(window.location.toString())
8470

8571
val index = storyState.storyboard.indices.binarySearch(frame)
8672
if (index >= 0) {
8773
url.searchParams.set("frame", index.toString())
8874
}
8975

90-
if (overviewVisible) {
91-
url.searchParams.set("overview", "true")
92-
} else {
93-
url.searchParams.delete("overview")
94-
}
95-
9676
window.history.replaceState(null, "", url.toString())
9777
}
9878
}

0 commit comments

Comments
 (0)