From 0225c2700ca380efff9c4b92286caca3ae218f54 Mon Sep 17 00:00:00 2001 From: Oleksandr Balan Date: Sun, 14 Aug 2022 10:54:49 +0200 Subject: [PATCH] Refactor page curl config --- .../pagecurl/components/SettingsPopup.kt | 22 +- .../screens/SettingsPageCurlScreen.kt | 50 ++- .../pagecurl/screens/StatePageCurlScreen.kt | 61 ++-- .../eu/wewox/pagecurl/config/CurlConfig.kt | 186 ----------- .../wewox/pagecurl/config/PageCurlConfig.kt | 304 ++++++++++++++++++ .../kotlin/eu/wewox/pagecurl/page/CurlDraw.kt | 26 +- .../eu/wewox/pagecurl/page/CurlGesture.kt | 27 +- .../kotlin/eu/wewox/pagecurl/page/PageCurl.kt | 29 +- .../eu/wewox/pagecurl/page/PageCurlState.kt | 13 +- .../eu/wewox/pagecurl/page/TapGesture.kt | 13 +- 10 files changed, 418 insertions(+), 313 deletions(-) delete mode 100644 pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/CurlConfig.kt create mode 100644 pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/PageCurlConfig.kt diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/components/SettingsPopup.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/components/SettingsPopup.kt index c0d3943..a4399b2 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/components/SettingsPopup.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/components/SettingsPopup.kt @@ -20,13 +20,11 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Popup import androidx.compose.ui.window.PopupProperties import eu.wewox.pagecurl.ExperimentalPageCurlApi -import eu.wewox.pagecurl.config.InteractionConfig -import eu.wewox.pagecurl.config.copy +import eu.wewox.pagecurl.config.PageCurlConfig @Composable internal fun SettingsPopup( - interaction: InteractionConfig, - onConfigChange: (InteractionConfig) -> Unit, + config: PageCurlConfig, onDismiss: () -> Unit, ) { Popup( @@ -49,26 +47,26 @@ internal fun SettingsPopup( .padding(horizontal = 10.dp) SwitchRow( text = "Forward drag enabled", - enabled = interaction.drag.forward.enabled, - onChanged = { onConfigChange(interaction.copy(dragForwardEnabled = it)) }, + enabled = config.dragForwardEnabled, + onChanged = { config.dragForwardEnabled = it }, modifier = switchRowModifier ) SwitchRow( text = "Backward drag enabled", - enabled = interaction.drag.backward.enabled, - onChanged = { onConfigChange(interaction.copy(dragBackwardEnabled = it)) }, + enabled = config.dragBackwardEnabled, + onChanged = { config.dragBackwardEnabled = it }, modifier = switchRowModifier ) SwitchRow( text = "Forward tap enabled", - enabled = interaction.tap.forward.enabled, - onChanged = { onConfigChange(interaction.copy(tapForwardEnabled = it)) }, + enabled = config.tapForwardEnabled, + onChanged = { config.tapForwardEnabled = it }, modifier = switchRowModifier ) SwitchRow( text = "Backward tap enabled", - enabled = interaction.tap.backward.enabled, - onChanged = { onConfigChange(interaction.copy(tapBackwardEnabled = it)) }, + enabled = config.tapBackwardEnabled, + onChanged = { config.tapBackwardEnabled = it }, modifier = switchRowModifier ) } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt index fb09944..f063f1f 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/SettingsPageCurlScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.center @@ -17,8 +18,7 @@ import eu.wewox.pagecurl.ExperimentalPageCurlApi import eu.wewox.pagecurl.HowToPageData import eu.wewox.pagecurl.components.HowToPage import eu.wewox.pagecurl.components.SettingsPopup -import eu.wewox.pagecurl.config.InteractionConfig -import eu.wewox.pagecurl.config.PageCurlConfig +import eu.wewox.pagecurl.config.rememberPageCurlConfig import eu.wewox.pagecurl.page.PageCurl import eu.wewox.pagecurl.page.rememberPageCurlState @@ -26,45 +26,31 @@ import eu.wewox.pagecurl.page.rememberPageCurlState fun SettingsPageCurlScreen() { Box(Modifier.fillMaxSize()) { val pages = remember { HowToPageData.interactionHowToPages } - val state = rememberPageCurlState(max = pages.size) - var showPopup by remember { mutableStateOf(false) } + var showPopup by rememberSaveable { mutableStateOf(false) } - // Create a mutable interaction config with custom tap interaction - // In SettingsPopup config is mutated - var interaction by remember { - mutableStateOf( - InteractionConfig( - tap = InteractionConfig.Tap( - custom = InteractionConfig.Tap.CustomInteraction(true) { size, position -> - // Detect tap somewhere in the center with 64 radius and show popup - if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) { - showPopup = true - true - } else { - false - } - } - ) - ) + val state = rememberPageCurlState( + max = pages.size, + config = rememberPageCurlConfig( + onCustomTap = { size, position -> + // Detect tap somewhere in the center with 64 radius and show popup + if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) { + showPopup = true + true + } else { + false + } + } ) - } + ) - PageCurl( - state = state, - config = PageCurlConfig( - interaction = interaction - ) - ) { index -> + PageCurl(state = state) { index -> HowToPage(index, pages[index]) } if (showPopup) { SettingsPopup( - interaction = interaction, - onConfigChange = { - interaction = it - }, + config = state.config, onDismiss = { showPopup = false } diff --git a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StatePageCurlScreen.kt b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StatePageCurlScreen.kt index fe7b34c..0a5e02a 100644 --- a/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StatePageCurlScreen.kt +++ b/demo/src/main/kotlin/eu/wewox/pagecurl/screens/StatePageCurlScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.material.Button import androidx.compose.material.Card import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -30,9 +31,7 @@ import eu.wewox.pagecurl.ExperimentalPageCurlApi import eu.wewox.pagecurl.HowToPageData import eu.wewox.pagecurl.components.HowToPage import eu.wewox.pagecurl.components.ZoomOutLayout -import eu.wewox.pagecurl.config.InteractionConfig -import eu.wewox.pagecurl.config.PageCurlConfig -import eu.wewox.pagecurl.config.copy +import eu.wewox.pagecurl.config.rememberPageCurlConfig import eu.wewox.pagecurl.page.PageCurl import eu.wewox.pagecurl.page.PageCurlState import eu.wewox.pagecurl.page.rememberPageCurlState @@ -44,28 +43,34 @@ import kotlinx.coroutines.launch fun StatePageCurlScreen() { Box(Modifier.fillMaxSize()) { val pages = remember { HowToPageData.interactionHowToPages } - val state = rememberPageCurlState(max = pages.size) - var zoomOut by remember { mutableStateOf(false) } - - val interactionConfig = remember { - InteractionConfig( - tap = InteractionConfig.Tap( - custom = InteractionConfig.Tap.CustomInteraction(true) { size, position -> - // When PageCurl is zoomed out then zoom back in - // Else detect tap somewhere in the center with 64 radius and zoom out a PageCurl - if (zoomOut) { - zoomOut = false - true - } else if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) { - zoomOut = true - true - } else { - false - } + val state = rememberPageCurlState( + max = pages.size, + config = rememberPageCurlConfig( + onCustomTap = { size, position -> + // When PageCurl is zoomed out then zoom back in + // Else detect tap somewhere in the center with 64 radius and zoom out a PageCurl + if (zoomOut) { + zoomOut = false + true + } else if ((position - size.center.toOffset()).getDistance() < 64.dp.toPx()) { + zoomOut = true + true + } else { + false } - ) + } ) + ) + + // Disable all state interactions when PageCurl is zoomed out + LaunchedEffect(zoomOut) { + with(state.config) { + dragForwardEnabled = !zoomOut + dragBackwardEnabled = !zoomOut + tapForwardEnabled = !zoomOut + tapBackwardEnabled = !zoomOut + } } ZoomOutLayout( @@ -79,17 +84,7 @@ fun StatePageCurlScreen() { shape = RoundedCornerShape(cornersAndElevation), elevation = cornersAndElevation, ) { - PageCurl( - state = state, - config = PageCurlConfig( - interaction = interactionConfig.copy( - dragForwardEnabled = !zoomOut, - dragBackwardEnabled = !zoomOut, - tapForwardEnabled = !zoomOut, - tapBackwardEnabled = !zoomOut, - ) - ) - ) { index -> + PageCurl(state = state) { index -> HowToPage(index, pages[index]) } } diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/CurlConfig.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/CurlConfig.kt deleted file mode 100644 index 17f0046..0000000 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/CurlConfig.kt +++ /dev/null @@ -1,186 +0,0 @@ -package eu.wewox.pagecurl.config - -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.DpOffset -import androidx.compose.ui.unit.IntSize -import androidx.compose.ui.unit.dp -import eu.wewox.pagecurl.ExperimentalPageCurlApi - -/** - * The configuration for PageCurl. - * - * @property curl Configures how page curl looks like. - * @property interaction Configures interactions with a page curl. Such as if drag or tap to go on next or previous - * page is allowed, etc. - */ -@ExperimentalPageCurlApi -public data class PageCurlConfig( - val curl: CurlConfig = CurlConfig(), - val interaction: InteractionConfig = InteractionConfig(), -) - -/** - * Configures how page curl looks like. - * - * @property backPage Configures how back-page looks like (color, content alpha). - * @property shadow Configures how page shadow looks like (color, alpha, radius). - */ -@ExperimentalPageCurlApi -public data class CurlConfig( - val backPage: BackPageConfig = BackPageConfig(), - val shadow: ShadowConfig = ShadowConfig(), -) - -/** - * Configures how back-page of the page curl looks like. - * - * @property color Color of the back-page. In majority of use-cases it should be set to the content background color. - * @property contentAlpha The alpha which defines how content is "seen through" the back-page. From 0 (nothing is - * visible) to 1 (everything is visible). - */ -@ExperimentalPageCurlApi -public data class BackPageConfig( - val color: Color = Color.White, - val contentAlpha: Float = 0.1f, -) - -/** - * Configures how page shadow looks like. - * - * @property color The color of the shadow. In majority of use-cases it should be set to the inverted color to the - * content background color. Should be a solid color, see [alpha] to adjust opacity. - * @property alpha The alpha of the [color]. - * @property radius Defines how big the shadow is. - * @property offset Defines how shadow is shifted from the page. A little shift may add more realism. - */ -@ExperimentalPageCurlApi -public data class ShadowConfig( - val color: Color = Color.Black, - val alpha: Float = 0.2f, - val radius: Dp = 15.dp, - val offset: DpOffset = DpOffset((-5).dp, 0.dp), -) - -/** - * Configures interactions with a page curl. - * - * @property drag Configures drag interactions. - * @property tap Configures tap interactions. - */ -@ExperimentalPageCurlApi -public data class InteractionConfig( - val drag: Drag = Drag(), - val tap: Tap = Tap(), -) { - - /** - * Configures drag interactions. - * - * @property forward Configures forward drag interaction. - * @property backward Configures backward drag interaction. - */ - @ExperimentalPageCurlApi - public data class Drag( - val forward: Interaction = Interaction(true, rightHalf(), leftHalf()), - val backward: Interaction = Interaction(true, forward.end, forward.start), - ) { - - /** - * The drag interaction setting. - * - * @property enabled True if this interaction is enabled or not. - * @property start Defines a rectangle where interaction should start. The rectangle coordinates are relative - * (from 0 to 1) and then scaled to the PageCurl bounds. - * @property end Defines a rectangle where interaction should end. The rectangle coordinates are relative - * (from 0 to 1) and then scaled to the PageCurl bounds. - */ - @ExperimentalPageCurlApi - public data class Interaction( - val enabled: Boolean, - val start: Rect = Rect.Zero, - val end: Rect = Rect.Zero, - ) - } - - /** - * Configures tap interactions. - * - * @property forward Configures forward tap interaction. - * @property backward Configures backward tap interaction. - * @property custom The custom tap interaction. Could be provided to implement custom taps in the PageCurl, e.g. to - * capture taps in the center, etc. - */ - @ExperimentalPageCurlApi - public data class Tap( - val forward: Interaction = Interaction(true, rightHalf()), - val backward: Interaction = Interaction(true, leftHalf()), - val custom: CustomInteraction = CustomInteraction(false) - ) { - - /** - * The tap interaction setting. - * - * @property enabled True if this interaction is enabled or not. - * @property target Defines a rectangle where interaction captured. The rectangle coordinates are relative - * (from 0 to 1) and then scaled to the PageCurl bounds. - */ - @ExperimentalPageCurlApi - public data class Interaction( - val enabled: Boolean, - val target: Rect = Rect.Zero, - ) - - /** - * The custom tap interaction setting. - * - * @property enabled True if this interaction is enabled or not. - * @property onTap The lambda to invoke to check if tap is handled by custom tap or not. Receives the density - * scope, the PageCurl size and tap position. Returns true if tap is handled and false otherwise. - */ - @ExperimentalPageCurlApi - public data class CustomInteraction( - val enabled: Boolean, - val onTap: Density.(IntSize, Offset) -> Boolean = { _, _ -> false }, - ) - } -} - -/** - * The utility function to create a new copy of the [InteractionConfig] with different enabled states of each - * interactions. - * - * @param dragForwardEnabled True to enable forward drag interaction. - * @param dragBackwardEnabled True to enable backward drag interaction. - * @param tapForwardEnabled True to enable forward tap interaction. - * @param tapBackwardEnabled True to enable backward tap interaction. - */ -@ExperimentalPageCurlApi -public fun InteractionConfig.copy( - dragForwardEnabled: Boolean = drag.forward.enabled, - dragBackwardEnabled: Boolean = drag.backward.enabled, - tapForwardEnabled: Boolean = tap.forward.enabled, - tapBackwardEnabled: Boolean = tap.backward.enabled, -): InteractionConfig = copy( - drag = drag.copy( - forward = drag.forward.copy(enabled = dragForwardEnabled), - backward = drag.backward.copy(enabled = dragBackwardEnabled) - ), - tap = tap.copy( - forward = tap.forward.copy(enabled = tapForwardEnabled), - backward = tap.backward.copy(enabled = tapBackwardEnabled) - ) -) - -/** - * The left half of the PageCurl. - */ -private fun leftHalf(): Rect = Rect(0.0f, 0.0f, 0.5f, 1.0f) - -/** - * The right half of the PageCurl. - */ -private fun rightHalf(): Rect = Rect(0.5f, 0.0f, 1.0f, 1.0f) diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/PageCurlConfig.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/PageCurlConfig.kt new file mode 100644 index 0000000..fcd9571 --- /dev/null +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/config/PageCurlConfig.kt @@ -0,0 +1,304 @@ +package eu.wewox.pagecurl.config + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import eu.wewox.pagecurl.ExperimentalPageCurlApi + +/** + * Creates a PageCurlConfig with the default properties and memorizes it. + * + * @param backPageColor Color of the back-page. In majority of use-cases it should be set to the content background + * color. + * @param backPageContentAlpha The alpha which defines how content is "seen through" the back-page. From 0 (nothing + * is visible) to 1 (everything is visible). + * @param shadowColor The color of the shadow. In majority of use-cases it should be set to the inverted color to the + * content background color. Should be a solid color, see [alpha] to adjust opacity. + * @param shadowAlpha The alpha of the [color]. + * @param shadowRadius Defines how big the shadow is. + * @param shadowOffset Defines how shadow is shifted from the page. A little shift may add more realism. + * @param dragForwardEnabled True if forward drag interaction is enabled or not. + * @param dragBackwardEnabled True if backward drag interaction is enabled or not. + * @param tapForwardEnabled True if forward tap interaction is enabled or not. + * @param tapBackwardEnabled True if backward tap interaction is enabled or not. + * @param tapCustomEnabled True if custom tap interaction is enabled or not, see [onCustomTap]. + * @param dragForwardInteraction The forward drag interaction setting. + * @param dragBackwardInteraction The backward drag interaction setting. + * @param tapForwardInteraction The forward tap interaction setting. + * @param tapBackwardInteraction The backward tap interaction setting. + * @param onCustomTap The lambda to invoke to check if tap is handled by custom tap or not. Receives the density + * scope, the PageCurl size and tap position. Returns true if tap is handled and false otherwise. + */ +@ExperimentalPageCurlApi +@Composable +public fun rememberPageCurlConfig( + backPageColor: Color = Color.White, + backPageContentAlpha: Float = 0.1f, + shadowColor: Color = Color.Black, + shadowAlpha: Float = 0.2f, + shadowRadius: Dp = 15.dp, + shadowOffset: DpOffset = DpOffset((-5).dp, 0.dp), + dragForwardEnabled: Boolean = true, + dragBackwardEnabled: Boolean = true, + tapForwardEnabled: Boolean = true, + tapBackwardEnabled: Boolean = true, + tapCustomEnabled: Boolean = true, + dragForwardInteraction: PageCurlConfig.DragInteraction = PageCurlConfig.DragInteraction(rightHalf(), leftHalf()), + dragBackwardInteraction: PageCurlConfig.DragInteraction = PageCurlConfig.DragInteraction(leftHalf(), rightHalf()), + tapForwardInteraction: PageCurlConfig.TapInteraction = PageCurlConfig.TapInteraction(rightHalf()), + tapBackwardInteraction: PageCurlConfig.TapInteraction = PageCurlConfig.TapInteraction(leftHalf()), + onCustomTap: Density.(IntSize, Offset) -> Boolean = { _, _ -> false }, +): PageCurlConfig = + rememberSaveable( + saver = listSaver( + save = { + listOf( + (it.backPageColor.value shr 32).toInt(), + it.backPageContentAlpha, + (it.shadowColor.value shr 32).toInt(), + it.shadowAlpha, + it.shadowRadius.value, + it.shadowOffset.x.value, + it.shadowOffset.y.value, + it.dragForwardEnabled, + it.dragBackwardEnabled, + it.tapForwardEnabled, + it.tapBackwardEnabled, + it.tapCustomEnabled, + it.dragForwardInteraction.start.topLeft.x, + it.dragForwardInteraction.start.topLeft.y, + it.dragForwardInteraction.start.bottomRight.x, + it.dragForwardInteraction.start.bottomRight.y, + it.dragForwardInteraction.end.topLeft.x, + it.dragForwardInteraction.end.topLeft.y, + it.dragForwardInteraction.end.bottomRight.x, + it.dragForwardInteraction.end.bottomRight.y, + it.dragBackwardInteraction.start.topLeft.x, + it.dragBackwardInteraction.start.topLeft.y, + it.dragBackwardInteraction.start.bottomRight.x, + it.dragBackwardInteraction.start.bottomRight.y, + it.dragBackwardInteraction.end.topLeft.x, + it.dragBackwardInteraction.end.topLeft.y, + it.dragBackwardInteraction.end.bottomRight.x, + it.dragBackwardInteraction.end.bottomRight.y, + it.tapForwardInteraction.target.topLeft.x, + it.tapForwardInteraction.target.topLeft.y, + it.tapForwardInteraction.target.bottomRight.x, + it.tapForwardInteraction.target.bottomRight.y, + it.tapBackwardInteraction.target.topLeft.x, + it.tapBackwardInteraction.target.topLeft.y, + it.tapBackwardInteraction.target.bottomRight.x, + it.tapBackwardInteraction.target.bottomRight.y, + ) + }, + restore = { + PageCurlConfig( + Color(it[0] as Int), + it[1] as Float, + Color(it[2] as Int), + it[3] as Float, + Dp(it[4] as Float), + DpOffset(Dp(it[5] as Float), Dp(it[6] as Float)), + it[7] as Boolean, + it[8] as Boolean, + it[9] as Boolean, + it[10] as Boolean, + it[11] as Boolean, + PageCurlConfig.DragInteraction( + Rect(it[12] as Float, it[13] as Float, it[14] as Float, it[15] as Float), + Rect(it[16] as Float, it[17] as Float, it[18] as Float, it[19] as Float), + ), + PageCurlConfig.DragInteraction( + Rect(it[20] as Float, it[21] as Float, it[22] as Float, it[23] as Float), + Rect(it[24] as Float, it[25] as Float, it[26] as Float, it[27] as Float), + ), + PageCurlConfig.TapInteraction( + Rect(it[28] as Float, it[29] as Float, it[30] as Float, it[31] as Float), + ), + PageCurlConfig.TapInteraction( + Rect(it[32] as Float, it[33] as Float, it[34] as Float, it[35] as Float), + ), + onCustomTap + ) + } + ) + ) { + PageCurlConfig( + backPageColor = backPageColor, + backPageContentAlpha = backPageContentAlpha, + shadowColor = shadowColor, + shadowAlpha = shadowAlpha, + shadowRadius = shadowRadius, + shadowOffset = shadowOffset, + dragForwardEnabled = dragForwardEnabled, + dragBackwardEnabled = dragBackwardEnabled, + tapForwardEnabled = tapForwardEnabled, + tapBackwardEnabled = tapBackwardEnabled, + tapCustomEnabled = tapCustomEnabled, + dragForwardInteraction = dragForwardInteraction, + dragBackwardInteraction = dragBackwardInteraction, + tapForwardInteraction = tapForwardInteraction, + tapBackwardInteraction = tapBackwardInteraction, + onCustomTap = onCustomTap + ) + } + +/** + * The configuration for PageCurl. + * + * @param backPageColor Color of the back-page. In majority of use-cases it should be set to the content background + * color. + * @param backPageContentAlpha The alpha which defines how content is "seen through" the back-page. From 0 (nothing + * is visible) to 1 (everything is visible). + * @param shadowColor The color of the shadow. In majority of use-cases it should be set to the inverted color to the + * content background color. Should be a solid color, see [shadowAlpha] to adjust opacity. + * @param shadowAlpha The alpha of the [shadowColor]. + * @param shadowRadius Defines how big the shadow is. + * @param shadowOffset Defines how shadow is shifted from the page. A little shift may add more realism. + * @param dragForwardEnabled True if forward drag interaction is enabled or not. + * @param dragBackwardEnabled True if backward drag interaction is enabled or not. + * @param tapForwardEnabled True if forward tap interaction is enabled or not. + * @param tapBackwardEnabled True if backward tap interaction is enabled or not. + * @param tapCustomEnabled True if custom tap interaction is enabled or not, see [onCustomTap]. + * @param dragForwardInteraction The forward drag interaction setting. + * @param dragBackwardInteraction The backward drag interaction setting. + * @param tapForwardInteraction The forward tap interaction setting. + * @param tapBackwardInteraction The backward tap interaction setting. + * @param onCustomTap The lambda to invoke to check if tap is handled by custom tap or not. Receives the density + * scope, the PageCurl size and tap position. Returns true if tap is handled and false otherwise. + */ +@ExperimentalPageCurlApi +public class PageCurlConfig( + backPageColor: Color, + backPageContentAlpha: Float, + shadowColor: Color, + shadowAlpha: Float, + shadowRadius: Dp, + shadowOffset: DpOffset, + dragForwardEnabled: Boolean, + dragBackwardEnabled: Boolean, + tapForwardEnabled: Boolean, + tapBackwardEnabled: Boolean, + tapCustomEnabled: Boolean, + dragForwardInteraction: DragInteraction, + dragBackwardInteraction: DragInteraction, + tapForwardInteraction: TapInteraction, + tapBackwardInteraction: TapInteraction, + public val onCustomTap: Density.(IntSize, Offset) -> Boolean, +) { + /** + * The color of the back-page. In majority of use-cases it should be set to the content background color. + */ + public var backPageColor: Color by mutableStateOf(backPageColor) + + /** + * The alpha which defines how content is "seen through" the back-page. From 0 (nothing is visible) to + * 1 (everything is visible). + */ + public var backPageContentAlpha: Float by mutableStateOf(backPageContentAlpha) + + /** + * The color of the shadow. In majority of use-cases it should be set to the inverted color to the content + * background color. Should be a solid color, see [shadowAlpha] to adjust opacity. + */ + public var shadowColor: Color by mutableStateOf(shadowColor) + + /** + * The alpha of the [shadowColor]. + */ + public var shadowAlpha: Float by mutableStateOf(shadowAlpha) + + /** + * Defines how big the shadow is. + */ + public var shadowRadius: Dp by mutableStateOf(shadowRadius) + + /** + * Defines how shadow is shifted from the page. A little shift may add more realism. + */ + public var shadowOffset: DpOffset by mutableStateOf(shadowOffset) + + /** + * True if forward drag interaction is enabled or not. + */ + public var dragForwardEnabled: Boolean by mutableStateOf(dragForwardEnabled) + + /** + * True if backward drag interaction is enabled or not. + */ + public var dragBackwardEnabled: Boolean by mutableStateOf(dragBackwardEnabled) + + /** + * True if forward tap interaction is enabled or not. + */ + public var tapForwardEnabled: Boolean by mutableStateOf(tapForwardEnabled) + + /** + * True if backward tap interaction is enabled or not. + */ + public var tapBackwardEnabled: Boolean by mutableStateOf(tapBackwardEnabled) + + /** + * True if custom tap interaction is enabled or not, see [onCustomTap]. + */ + public var tapCustomEnabled: Boolean by mutableStateOf(tapCustomEnabled) + + /** + * The forward drag interaction setting. + */ + public var dragForwardInteraction: DragInteraction by mutableStateOf(dragForwardInteraction) + + /** + * The backward drag interaction setting. + */ + public var dragBackwardInteraction: DragInteraction by mutableStateOf(dragBackwardInteraction) + + /** + * The forward tap interaction setting. + */ + public var tapForwardInteraction: TapInteraction by mutableStateOf(tapForwardInteraction) + + /** + * The backward tap interaction setting. + */ + public var tapBackwardInteraction: TapInteraction by mutableStateOf(tapBackwardInteraction) + + /** + * The drag interaction setting. + * + * @property start Defines a rectangle where interaction should start. The rectangle coordinates are relative + * (from 0 to 1) and then scaled to the PageCurl bounds. + * @property end Defines a rectangle where interaction should end. The rectangle coordinates are relative + * (from 0 to 1) and then scaled to the PageCurl bounds. + */ + public data class DragInteraction(val start: Rect, val end: Rect) + + /** + * The tap interaction setting. + * + * @property target Defines a rectangle where interaction captured. The rectangle coordinates are relative + * (from 0 to 1) and then scaled to the PageCurl bounds. + */ + public data class TapInteraction(val target: Rect) +} + +/** + * The left half of the PageCurl. + */ +private fun leftHalf(): Rect = Rect(0.0f, 0.0f, 0.5f, 1.0f) + +/** + * The right half of the PageCurl. + */ +private fun rightHalf(): Rect = Rect(0.5f, 0.0f, 1.0f, 1.0f) diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlDraw.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlDraw.kt index bb29b18..1d8d040 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlDraw.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlDraw.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import eu.wewox.pagecurl.ExperimentalPageCurlApi -import eu.wewox.pagecurl.config.CurlConfig +import eu.wewox.pagecurl.config.PageCurlConfig import eu.wewox.pagecurl.utils.Polygon import eu.wewox.pagecurl.utils.lineLineIntersection import eu.wewox.pagecurl.utils.rotate @@ -30,7 +30,7 @@ import kotlin.math.atan2 @ExperimentalPageCurlApi internal fun Modifier.drawCurl( - config: CurlConfig = CurlConfig(), + config: PageCurlConfig, posA: Offset, posB: Offset, ): Modifier = drawWithCache { @@ -114,7 +114,7 @@ private fun CacheDrawScope.prepareClippedContent( @ExperimentalPageCurlApi private fun CacheDrawScope.prepareCurl( - config: CurlConfig, + config: PageCurlConfig, topCurlOffset: Offset, bottomCurlOffset: Offset, ): ContentDrawScope.() -> Unit { @@ -176,8 +176,8 @@ private fun CacheDrawScope.prepareCurl( clipPath(polygon.toPath()) { this@result.drawContent() - val overlayAlpha = 1f - config.backPage.contentAlpha - drawRect(config.backPage.color.copy(alpha = overlayAlpha)) + val overlayAlpha = 1f - config.backPageContentAlpha + drawRect(config.backPageColor.copy(alpha = overlayAlpha)) } } } @@ -185,22 +185,20 @@ private fun CacheDrawScope.prepareCurl( @ExperimentalPageCurlApi private fun CacheDrawScope.prepareShadow( - config: CurlConfig, + config: PageCurlConfig, polygon: Polygon, angle: Float ): ContentDrawScope.() -> Unit { - val shadow = config.shadow - // Quick exit if no shadow is requested - if (shadow.alpha == 0f || shadow.radius == 0.dp) { + if (config.shadowAlpha == 0f || config.shadowRadius == 0.dp) { return { /* No shadow is requested */ } } // Prepare shadow parameters - val radius = shadow.radius.toPx() - val shadowColor = shadow.color.copy(alpha = shadow.alpha).toArgb() - val transparent = shadow.color.copy(alpha = 0f).toArgb() - val shadowOffset = Offset(-shadow.offset.x.toPx(), shadow.offset.y.toPx()) + val radius = config.shadowRadius.toPx() + val shadowColor = config.shadowColor.copy(alpha = config.shadowAlpha).toArgb() + val transparent = config.shadowColor.copy(alpha = 0f).toArgb() + val shadowOffset = Offset(-config.shadowOffset.x.toPx(), config.shadowOffset.y.toPx()) .rotate(2 * Math.PI.toFloat() - angle) // Prepare shadow paint with a shadow layer @@ -208,7 +206,7 @@ private fun CacheDrawScope.prepareShadow( val frameworkPaint = asFrameworkPaint() frameworkPaint.color = transparent frameworkPaint.setShadowLayer( - shadow.radius.toPx(), + config.shadowRadius.toPx(), shadowOffset.x, shadowOffset.y, shadowColor diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlGesture.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlGesture.kt index 0f4520f..3fbdeda 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlGesture.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/CurlGesture.kt @@ -10,10 +10,10 @@ import androidx.compose.foundation.gestures.drag import androidx.compose.foundation.gestures.forEachGesture import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.util.VelocityTracker import eu.wewox.pagecurl.ExperimentalPageCurlApi -import eu.wewox.pagecurl.config.InteractionConfig import eu.wewox.pagecurl.utils.multiply import eu.wewox.pagecurl.utils.rotate import kotlinx.coroutines.CoroutineScope @@ -25,20 +25,22 @@ internal fun Modifier.curlGesture( state: PageCurlState.InternalState, enabled: Boolean, scope: CoroutineScope, - direction: InteractionConfig.Drag.Interaction, - start: Edge, - end: Edge, + targetStart: Rect, + targetEnd: Rect, + edgeStart: Edge, + edgeEnd: Edge, edge: Animatable, onChange: () -> Unit, ): Modifier = curlGesture( key = state, enabled = enabled, - direction = direction, + targetStart = targetStart, + targetEnd = targetEnd, onStart = { scope.launch { state.animateJob?.cancel() - edge.snapTo(start) + edge.snapTo(edgeStart) } }, onCurl = { a, b -> @@ -49,16 +51,16 @@ internal fun Modifier.curlGesture( onEnd = { scope.launch { try { - edge.animateTo(end) + edge.animateTo(edgeEnd) } finally { onChange() - edge.snapTo(start) + edge.snapTo(edgeStart) } } }, onCancel = { scope.launch { - edge.animateTo(start) + edge.animateTo(edgeStart) } }, ) @@ -67,7 +69,8 @@ internal fun Modifier.curlGesture( internal fun Modifier.curlGesture( key: Any?, enabled: Boolean, - direction: InteractionConfig.Drag.Interaction, + targetStart: Rect, + targetEnd: Rect, onStart: () -> Unit, onCurl: (Offset, Offset) -> Unit, onEnd: () -> Unit, @@ -79,8 +82,8 @@ internal fun Modifier.curlGesture( // Use velocity tracker to support flings val velocityTracker = VelocityTracker() - val startRect by lazy { direction.start.multiply(size) } - val endRect by lazy { direction.end.multiply(size) } + val startRect by lazy { targetStart.multiply(size) } + val endRect by lazy { targetEnd.multiply(size) } forEachGesture { awaitPointerEventScope { val down = awaitFirstDown(requireUnconsumed = false) diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt index 147f00f..650f7b2 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurl.kt @@ -15,7 +15,6 @@ import eu.wewox.pagecurl.config.PageCurlConfig * * @param state The state of the PageCurl. Use this to programmatically change the current page or observe changes. * @param modifier The modifier for this composable. - * @param config The configuration for PageCurl. Configures how page curl looks like and interacts. * @param content The content lambda to provide the page composable. Receives the page number. */ @ExperimentalPageCurlApi @@ -23,7 +22,6 @@ import eu.wewox.pagecurl.config.PageCurlConfig public fun PageCurl( state: PageCurlState, modifier: Modifier = Modifier, - config: PageCurlConfig = PageCurlConfig(), content: @Composable (Int) -> Unit ) { val scope = rememberCoroutineScope() @@ -34,32 +32,35 @@ public fun PageCurl( val internalState by rememberUpdatedState(state.internalState ?: return@BoxWithConstraints) + val config by rememberUpdatedState(state.config) + Box( Modifier .curlGesture( state = internalState, - enabled = config.interaction.drag.forward.enabled && updatedCurrent < state.max - 1, + enabled = state.config.dragForwardEnabled && updatedCurrent < state.max - 1, scope = scope, - direction = config.interaction.drag.forward, - start = internalState.rightEdge, - end = internalState.leftEdge, + targetStart = config.dragForwardInteraction.start, + targetEnd = config.dragForwardInteraction.end, + edgeStart = internalState.rightEdge, + edgeEnd = internalState.leftEdge, edge = internalState.forward, onChange = { state.current = updatedCurrent + 1 } ) .curlGesture( state = internalState, - enabled = config.interaction.drag.backward.enabled && updatedCurrent > 0, + enabled = state.config.dragBackwardEnabled && updatedCurrent > 0, scope = scope, - direction = config.interaction.drag.backward, - start = internalState.leftEdge, - end = internalState.rightEdge, + targetStart = config.dragBackwardInteraction.start, + targetEnd = config.dragBackwardInteraction.end, + edgeStart = internalState.leftEdge, + edgeEnd = internalState.rightEdge, edge = internalState.backward, onChange = { state.current = updatedCurrent - 1 } ) .tapGesture( - state = internalState, + config = config, scope = scope, - interaction = config.interaction.tap, onTapForward = state::next, onTapBackward = state::prev, ) @@ -69,13 +70,13 @@ public fun PageCurl( } if (updatedCurrent < state.max) { - Box(Modifier.drawCurl(config.curl, internalState.forward.value.top, internalState.forward.value.bottom)) { + Box(Modifier.drawCurl(config, internalState.forward.value.top, internalState.forward.value.bottom)) { content(updatedCurrent) } } if (updatedCurrent > 0) { - Box(Modifier.drawCurl(config.curl, internalState.backward.value.top, internalState.backward.value.bottom)) { + Box(Modifier.drawCurl(config, internalState.backward.value.top, internalState.backward.value.bottom)) { content(updatedCurrent - 1) } } diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt index 34c7756..5c35f9f 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/PageCurlState.kt @@ -16,6 +16,8 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Size import androidx.compose.ui.unit.Constraints import eu.wewox.pagecurl.ExperimentalPageCurlApi +import eu.wewox.pagecurl.config.PageCurlConfig +import eu.wewox.pagecurl.config.rememberPageCurlConfig import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.coroutineScope @@ -27,6 +29,7 @@ import kotlinx.coroutines.withContext * * @param max The max number of pages. * @param initialCurrent The initial current page. + * @param config The configuration for PageCurl. * @return The remembered [PageCurlState]. */ @ExperimentalPageCurlApi @@ -34,6 +37,7 @@ import kotlinx.coroutines.withContext public fun rememberPageCurlState( max: Int, initialCurrent: Int = 0, + config: PageCurlConfig = rememberPageCurlConfig() ): PageCurlState = rememberSaveable( max, initialCurrent, @@ -41,15 +45,17 @@ public fun rememberPageCurlState( save = { it.current }, restore = { PageCurlState( - max = it, - initialCurrent = initialCurrent + max = max, + initialCurrent = it, + config = config, ) } ) ) { PageCurlState( max = max, - initialCurrent = initialCurrent + initialCurrent = initialCurrent, + config = config, ) } @@ -62,6 +68,7 @@ public fun rememberPageCurlState( @ExperimentalPageCurlApi public class PageCurlState( public val max: Int, + public val config: PageCurlConfig, initialCurrent: Int = 0, ) { /** diff --git a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/TapGesture.kt b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/TapGesture.kt index 91871a6..1776b1e 100644 --- a/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/TapGesture.kt +++ b/pagecurl/src/main/kotlin/eu/wewox/pagecurl/page/TapGesture.kt @@ -6,19 +6,18 @@ import androidx.compose.foundation.gestures.waitForUpOrCancellation import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import eu.wewox.pagecurl.ExperimentalPageCurlApi -import eu.wewox.pagecurl.config.InteractionConfig +import eu.wewox.pagecurl.config.PageCurlConfig import eu.wewox.pagecurl.utils.multiply import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @ExperimentalPageCurlApi internal fun Modifier.tapGesture( - state: PageCurlState.InternalState, + config: PageCurlConfig, scope: CoroutineScope, - interaction: InteractionConfig.Tap, onTapForward: suspend () -> Unit, onTapBackward: suspend () -> Unit, -): Modifier = pointerInput(interaction, state) { +): Modifier = pointerInput(config) { forEachGesture { awaitPointerEventScope { val down = awaitFirstDown().also { it.consume() } @@ -28,18 +27,18 @@ internal fun Modifier.tapGesture( return@awaitPointerEventScope } - if (interaction.custom.enabled && interaction.custom.onTap(this, size, up.position)) { + if (config.tapCustomEnabled && config.onCustomTap(this, size, up.position)) { return@awaitPointerEventScope } - if (interaction.forward.enabled && interaction.forward.target.multiply(size).contains(up.position)) { + if (config.tapForwardEnabled && config.tapForwardInteraction.target.multiply(size).contains(up.position)) { scope.launch { onTapForward() } return@awaitPointerEventScope } - if (interaction.backward.enabled && interaction.backward.target.multiply(size).contains(up.position)) { + if (config.tapBackwardEnabled && config.tapBackwardInteraction.target.multiply(size).contains(up.position)) { scope.launch { onTapBackward() }