Stefan Oltmann
10/15/2024, 12:03 AMmouseEnter
and mouseExit
has some issues, as I couldn't find built-in support. It has its flaws. What’s the best way to implement proper tooltips?Daniel Weidensdörfer
10/15/2024, 7:23 PMModifier.hoverable(interactionSource, true)
for checking hovers. You could try it for your tooltip implementation.
val interactionSource = remember { MutableInteractionSource() }
LaunchedEffect(Unit) {
interactionSource.interactions.collect { interaction ->
when (interaction) {
is HoverInteraction.Enter -> {}
is HoverInteraction.Exit -> {}
}
}
}
Its probably possible to put a delay there as well, to show tooltips delayed.Kyle Eichlin
10/16/2024, 11:48 PMimport androidx.compose.foundation.background
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun Tooltip(
tooltipText: String?,
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Black,
textColor: Color = Color.White,
textStyle: TextStyle = MaterialTheme.typography.bodyMedium,
padding: Int = 8,
delayMillis: Long = 500L,
content: @Composable () -> Unit
) {
var size by remember { mutableStateOf(IntSize.Zero) }
var isTooltipVisible by remember { mutableStateOf(false) }
val interactionSource = remember { MutableInteractionSource() }
val isHovered by interactionSource.collectIsHoveredAsState()
val coroutineScope = rememberCoroutineScope()
var currentJob by remember { mutableStateOf<Job?>(null) }
LaunchedEffect(isHovered) {
if (isHovered) {
currentJob = coroutineScope.launch {
delay(delayMillis)
isTooltipVisible = true
}
} else {
currentJob?.cancel()
currentJob = null
isTooltipVisible = false
}
}
Box(
modifier = modifier
.hoverable(interactionSource)
.onGloballyPositioned { coordinates ->
size = coordinates.size
}
) {
content()
if (isTooltipVisible && tooltipText != null) {
Popup(
alignment = Alignment.Center,
offset = IntOffset(0, -size.height.plus(10)),
) {
Box(
modifier = Modifier
.background(
color = backgroundColor,
shape = MaterialTheme.shapes.small
)
.padding(padding.dp)
) {
BasicText(
text = tooltipText,
style = textStyle.copy(color = textColor)
)
}
}
}
}
}
And then how I use it:
Tooltip(tooltipText = "Copy Response to Clipboard") {
Icon(
painter = painterResource(Res.drawable.copy_fill),
contentDescription = "Copy to Clipboard",
tint = Color.Black
)
}
Kyle Eichlin
10/16/2024, 11:50 PMStefan Oltmann
10/19/2024, 9:09 PM