Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compose/ui/ui/api/desktop/ui.api
Original file line number Diff line number Diff line change
Expand Up @@ -4332,6 +4332,7 @@ public final class androidx/compose/ui/window/DialogProperties {
public fun <init> (ZZZ)V
public synthetic fun <init> (ZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (ZZZZZJILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (ZZZZZJLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun equals (Ljava/lang/Object;)Z
public final fun getDismissOnBackPress ()Z
public final fun getDismissOnClickOutside ()Z
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ internal fun easeInOutTimingFunction(progress: Float): Float = if (progress < 0.
(-2f * progress * progress) + (4f * progress) - 1f
}

internal fun easeOutTimingFunction(progress: Float): Float {
return -progress * (progress - 2f)
}

internal fun easeInTimingFunction(progress: Float): Float {
return progress * progress
}

internal suspend fun withAnimationProgress(
duration: Duration,
timingFunction: (Float) -> Float = ::easeInOutTimingFunction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,6 @@ internal fun rememberComposeSceneLayer(
layer.density = density
layer.layoutDirection = layoutDirection

DisposableEffect(Unit) {
onDispose {
layer.close()
}
}
return layer
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,23 @@
package androidx.compose.ui.window

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.animation.easeOutTimingFunction
import androidx.compose.ui.animation.withAnimationProgress
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.GraphicsLayerScope
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
Expand All @@ -46,12 +55,50 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.IntRect
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.center
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/**
* The default scrim opacity.
*/
private const val DefaultScrimOpacity = 0.6f
private const val DefaultScrimOpacity = 0.78f
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert default value here - it's picked from Android OS sources. It should be the same by default across the platforms

private val DefaultScrimColor = Color.Black.copy(alpha = DefaultScrimOpacity)
private const val AnimatedLayerAppearanceOffsetDp = 16f
private const val AnimatedLayerDisappearanceOffsetDp = 8f
private const val AnimatedLayerInitialAlphaProgress = 0.6f

/**
* Represents an animation scope for dialogs, allowing customization of dialog animations
* and associated visual properties.
*
* This interface provides methods to apply transformations and modify visual properties
* during dialog animations.
*
* Note: This API is experimental and may change in the future.
*/
@ExperimentalComposeUiApi
@Immutable
interface DialogAnimationScope {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good idea to introduce such API not in commonMain. My suggestion: introduce android-like animation for now in fork, but do NOT introduce such API for configuring in skikoMain. Instead we can start the thread with Google folks to add this in common in future.

We can still add another boolean flag to DialogProperties just to disable animation for compatibility purposes

/**
* Applies graphics layer transformations and customizations within the specified [GraphicsLayerScope].
* This method can be used to modify visual properties like rotation, scale, translation,
* shadow, and other effects.
*
* @param modify A lambda receiver of [GraphicsLayerScope] allowing customization of graphics layer properties.
*/
fun graphicsLayer(modify: GraphicsLayerScope.() -> Unit)

/**
* Defines the color of the scrim used during dialog animations.
*
* The scrim is a semi-transparent layer displayed behind the dialog to
* focus the user's attention on the foreground content. This property
* allows customization of the scrim's appearance to match desired visual
* aesthetics or themes.
*/
var scrimColor: Color
}

/**
* Properties used to customize the behavior of a [Dialog].
Expand All @@ -68,6 +115,8 @@ private val DefaultScrimColor = Color.Black.copy(alpha = DefaultScrimOpacity)
* @property useSoftwareKeyboardInset Whether the size of the dialog's content should be limited by
* software keyboard inset.
* @property scrimColor Color of background fill.
* @property onAppearEffect The effect to be applied when the dialog appears.
* @property onDisappearEffect The effect to be applied when the dialog disappears.
*/
@Immutable
actual class DialogProperties @ExperimentalComposeUiApi constructor(
Expand All @@ -77,6 +126,12 @@ actual class DialogProperties @ExperimentalComposeUiApi constructor(
val usePlatformInsets: Boolean = true,
val useSoftwareKeyboardInset: Boolean = true,
val scrimColor: Color = DefaultScrimColor,
@property:ExperimentalComposeUiApi
val onAppearEffect: suspend DialogAnimationScope.() -> Unit =
DialogAnimationScope::defaultDialogAppearEffect,
@property:ExperimentalComposeUiApi
val onDisappearEffect: suspend DialogAnimationScope.() -> Unit =
DialogAnimationScope::defaultDialogDisappearEffect,
) {
actual constructor(
dismissOnBackPress: Boolean,
Expand All @@ -91,6 +146,25 @@ actual class DialogProperties @ExperimentalComposeUiApi constructor(
scrimColor = DefaultScrimColor,
)

@ExperimentalComposeUiApi
constructor(
dismissOnBackPress: Boolean = true,
dismissOnClickOutside: Boolean = true,
usePlatformDefaultWidth: Boolean = true,
usePlatformInsets: Boolean = true,
useSoftwareKeyboardInset: Boolean = true,
scrimColor: Color = DefaultScrimColor,
) : this(
dismissOnBackPress = dismissOnBackPress,
dismissOnClickOutside = dismissOnClickOutside,
usePlatformDefaultWidth = usePlatformDefaultWidth,
usePlatformInsets = usePlatformInsets,
useSoftwareKeyboardInset = useSoftwareKeyboardInset,
scrimColor = scrimColor,
onAppearEffect = DialogAnimationScope::defaultDialogAppearEffect,
onDisappearEffect = DialogAnimationScope::defaultDialogDisappearEffect,
)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is DialogProperties) return false
Expand Down Expand Up @@ -169,14 +243,30 @@ private fun DialogLayout(
content: @Composable () -> Unit
) {
val currentContent by rememberUpdatedState(content)

val compositionContext = rememberCompositionContext()
var graphicsLayerScopeUpdate by remember { mutableStateOf<GraphicsLayerScope.() -> Unit>({}) }
val layer = rememberComposeSceneLayer(
focusable = true
)
layer.scrimColor = properties.scrimColor
layer.setKeyEventListener(onPreviewKeyEvent, onKeyEvent)
layer.setOutsidePointerEventListener(onOutsidePointerEvent)
val dialogAnimationScope = remember {
object : DialogAnimationScope {
override fun graphicsLayer(modify: GraphicsLayerScope.() -> Unit) {
graphicsLayerScopeUpdate = modify
}

override var scrimColor: Color
get() = layer.scrimColor ?: properties.scrimColor
set(value) { layer.scrimColor = value }
}
}
layer.Content {
LaunchedEffect(Unit) {
properties.onAppearEffect(dialogAnimationScope)
graphicsLayerScopeUpdate = {}
layer.scrimColor = properties.scrimColor
}
val platformInsets = properties.platformInsets
val containerSize = LocalWindowInfo.current.containerSize
val measurePolicy = rememberDialogMeasurePolicy(
Expand All @@ -192,11 +282,45 @@ private fun DialogLayout(
) {
Layout(
content = currentContent,
modifier = modifier,
modifier = Modifier.graphicsLayer(graphicsLayerScopeUpdate).then(modifier),
measurePolicy = measurePolicy
)
}
}

DisposableEffect(Unit) {
onDispose {
CoroutineScope(compositionContext.effectCoroutineContext).launch {
properties.onDisappearEffect(dialogAnimationScope)
layer.close()
}
}
}
}

private suspend fun DialogAnimationScope.defaultDialogAppearEffect() {
val initialScrimColor = this.scrimColor
withAnimationProgress(0.15.seconds, timingFunction = ::easeOutTimingFunction) { progress ->
val animatedAlpha =
AnimatedLayerInitialAlphaProgress + progress * (1f - AnimatedLayerInitialAlphaProgress)
this.scrimColor = initialScrimColor.copy(initialScrimColor.alpha * animatedAlpha)
this.graphicsLayer {
this.alpha = animatedAlpha
this.translationY = (AnimatedLayerAppearanceOffsetDp * (1f - progress)) * density
}
}
}

private suspend fun DialogAnimationScope.defaultDialogDisappearEffect() {
val initialScrimColor = this.scrimColor
withAnimationProgress(0.10.seconds, timingFunction = ::easeOutTimingFunction) { progress ->
val animatedAlpha = 1f - progress * AnimatedLayerInitialAlphaProgress
this.scrimColor = initialScrimColor.copy(initialScrimColor.alpha * animatedAlpha)
this.graphicsLayer {
this.alpha = animatedAlpha
this.translationY = -AnimatedLayerDisappearanceOffsetDp * progress * density
}
}
}

private val DialogProperties.platformInsets: PlatformInsets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package androidx.compose.ui.window

import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand Down Expand Up @@ -452,6 +453,12 @@ private fun PopupLayout(
)
}
}

DisposableEffect(Unit) {
onDispose {
layer.close()
}
}
}

private val PopupProperties.platformInsets: PlatformInsets
Expand Down