Kirill Grouchnikov
11/15/2020, 9:12 PMVadim Kapustin
11/16/2020, 7:43 AM@Composable
fun iconWithHint(
    asset: ImageAsset,
    modifier: Modifier = Modifier,
    tint: Color = AmbientContentColor.current, // for example, if something wrong, we can set color to Color.Red
    popupTimeout: Long = 500,
    popupShowTime: Long = 3000,
    hint: @Composable() () -> Unit // for example { Text(message) }
) {
    val entered = remember { mutableStateOf(0) }
    val popupPosition = remember { mutableStateOf(IntOffset(0, 0)) }
    var enterPosition: IntOffset
    Box(modifier) {
        Icon(asset,
            Modifier.pointerMoveFilter(
                onMove = { position ->
                    popupPosition.value = IntOffset(position.x.toInt(), position.y.toInt())
                    false
                },
                onEnter = {
                    if (entered.value == 0)
                        GlobalScope.launch {
                            entered.value = 1
                            delay(popupTimeout/2)
                            enterPosition = popupPosition.value
                            delay(popupTimeout/2)
                            if (entered.value == 1 && enterPosition == popupPosition.value)
                                entered.value = 2
                        }
                    false
                },
                onExit = {
                    entered.value = 0
                    false
                }
            ),
            tint
        )
        if (entered.value == 2) {
            GlobalScope.launch {
                val count = 10
                for (i in 0.until(count)) {
                    if (entered.value == 0) break
                    delay(popupShowTime/count)
                }
                entered.value = 0
            }
            Popup(
                offset = popupPosition.value
            ) {
                Surface(
                    modifier = Modifier.pointerMoveFilter(
                        onEnter = { false },
                        onExit = {
                            entered.value = 0
                            false
                        }
                    ),
                    shape = RoundedCornerShape(5.dp)
                ) {
                    hint()
                }
            }
        }
    }
}Igor Demin
11/16/2020, 11:40 AMHere's what happened:I rewrote this code:
Igor Demin
11/16/2020, 11:40 AMIgor Demin
11/16/2020, 11:40 AMimport androidx.compose.desktop.Window
import androidx.compose.foundation.clickable
import androidx.compose.material.Text
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offsetPx
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Providers
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.onDispose
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.staticAmbientOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerMoveFilter
import androidx.compose.ui.layout.globalPosition
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.unit.Duration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.inMilliseconds
import androidx.compose.ui.unit.milliseconds
import kotlinx.coroutines.delay
fun main() = Window {
    HintContainer {
        Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
            Text("Left text")
            WithHint(hint = {
                Surface(
                    Modifier.padding(top = 18.dp),
                    color = Color.LightGray,
                ) {
                    Text("Hint", Modifier.padding(4.dp))
                }
            }) {
                Text("Right text", Modifier.clickable {
                    println("Click")
                })
            }
        }
    }
}
val HintsAmbient = staticAmbientOf<SnapshotStateList<@Composable () -> Unit>>()
@Composable
fun HintContainer(children: @Composable () -> Unit) {
    val hints = remember<SnapshotStateList<@Composable () -> Unit>> { mutableStateListOf() }
    Providers(HintsAmbient provides hints) {
        children()
        Box {
            for (hint in hints) {
                hint()
            }
        }
    }
}
@Composable
fun WithHint(
    modifier: Modifier = Modifier,
    delay: Duration = 500.milliseconds,
    hint: @Composable () -> Unit, // for example { Text(message) }
    content: @Composable () -> Unit
) {
    val hints = HintsAmbient.current
    var hoverLocalPosition by remember { mutableStateOf<Offset?>(null) }
    var contentGlobalPosition by remember { mutableStateOf<Offset?>(null) }
    val hintContainer = remember {
        @Composable {
            if (contentGlobalPosition != null && hoverLocalPosition != null) {
                Box(
                    Modifier.offsetPx(
                        derivedStateOf { contentGlobalPosition!!.x + hoverLocalPosition!!.x },
                        derivedStateOf { contentGlobalPosition!!.y + hoverLocalPosition!!.y },
                    )
                ) {
                    hint()
                }
            }
        } as (@Composable () -> Unit)
    }
    if (hoverLocalPosition != null) {
        LaunchedEffect(Unit) {
            delay(delay.inMilliseconds())
            hints.add(hintContainer)
        }
    } else {
        hints.remove(hintContainer)
    }
    onDispose {
        hints.remove(hintContainer)
    }
    Box(
        modifier
            .pointerMoveFilter(
                onMove = {
                    hoverLocalPosition = it
                    false
                },
                onEnter = { false },
                onExit = {
                    hoverLocalPosition = null
                    false
                }
            )
            .onGloballyPositioned {
                contentGlobalPosition = it.globalPosition
            }
    ) {
        content()
    }
}Vadim Kapustin
11/17/2020, 8:27 AMSurface(shape = RoundedCornerShape(5.dp)) {
        hint()
}Modifier.offsetPx(
                        derivedStateOf { contentGlobalPosition!!.x + hoverLocalPosition!!.x },
                        derivedStateOf { contentGlobalPosition!!.y + hoverLocalPosition!!.y },
                    )if (contentGlobalPosition != null && hoverLocalPosition != null) {
                val offset = contentGlobalPosition!! + hoverLocalPosition!!
                Box(
                    Modifier.offsetPx(
                        derivedStateOf { offset.x },
                        derivedStateOf { offset.y },
                    )
                ) {
                    Surface(shape = RoundedCornerShape(5.dp)) {
                            hint()
                    }
                }
            }Vadim Kapustin
11/17/2020, 8:34 AM