Migrate Clickable semantics to Modifier.Node Test: Existing tests Relnote: N/A Change-Id: I714706b7cfa0cbe82e55e1b389ffd6dc0159d13e
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt index b249514..0e24104 100644 --- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt +++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/Clickable.kt
@@ -40,13 +40,16 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.modifier.ModifierLocalConsumer import androidx.compose.ui.modifier.ModifierLocalReadScope +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.SemanticsModifierNode +import androidx.compose.ui.platform.InspectorInfo import androidx.compose.ui.platform.debugInspectorInfo import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.SemanticsConfiguration import androidx.compose.ui.semantics.disabled import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.onLongClick import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.center import androidx.compose.ui.unit.toOffset import kotlinx.coroutines.CoroutineScope @@ -512,23 +515,6 @@ onLongClick: (() -> Unit)? = null, onClick: () -> Unit ): Modifier { - fun Modifier.clickSemantics() = this.semantics(mergeDescendants = true) { - if (role != null) { - this.role = role - } - // b/156468846: add long click semantics and double click if needed - onClick( - action = { onClick(); true }, - label = onClickLabel - ) - if (onLongClick != null) { - onLongClick(action = { onLongClick(); true }, label = onLongClickLabel) - } - if (!enabled) { - disabled() - } - } - fun Modifier.detectPressAndClickFromKey() = this.onKeyEvent { keyEvent -> when { enabled && keyEvent.isPress -> { @@ -555,11 +541,101 @@ else -> false } } - return this - .clickSemantics() - .detectPressAndClickFromKey() - .indication(interactionSource, indication) - .hoverable(enabled = enabled, interactionSource = interactionSource) - .focusableInNonTouchMode(enabled = enabled, interactionSource = interactionSource) - .then(gestureModifiers) + return this then + ClickableSemanticsElement( + enabled = enabled, + role = role, + onLongClickLabel = onLongClickLabel, + onLongClick = onLongClick, + onClickLabel = onClickLabel, + onClick = onClick + ) + .detectPressAndClickFromKey() + .indication(interactionSource, indication) + .hoverable(enabled = enabled, interactionSource = interactionSource) + .focusableInNonTouchMode(enabled = enabled, interactionSource = interactionSource) + .then(gestureModifiers) +} + +private class ClickableSemanticsElement( + private val enabled: Boolean, + private val role: Role?, + private val onLongClickLabel: String?, + private val onLongClick: (() -> Unit)?, + private val onClickLabel: String?, + private val onClick: () -> Unit +) : ModifierNodeElement<ClickableSemanticsNode>() { + override fun create() = ClickableSemanticsNode( + enabled = enabled, + role = role, + onLongClickLabel = onLongClickLabel, + onLongClick = onLongClick, + onClickLabel = onClickLabel, + onClick = onClick + ) + + override fun update(node: ClickableSemanticsNode) = node.also { + it.enabled = enabled + it.role = role + it.onLongClickLabel = onLongClickLabel + it.onLongClick = onLongClick + it.onClickLabel = onClickLabel + it.onClick = onClick + } + + override fun InspectorInfo.inspectableProperties() = Unit + + override fun hashCode(): Int { + var result = enabled.hashCode() + result = 31 * result + role.hashCode() + result = 31 * result + onLongClickLabel.hashCode() + result = 31 * result + onLongClick.hashCode() + result = 31 * result + onClickLabel.hashCode() + result = 31 * result + onClick.hashCode() + return result + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ClickableSemanticsElement) return false + + if (enabled != other.enabled) return false + if (role != other.role) return false + if (onLongClickLabel != other.onLongClickLabel) return false + if (onLongClick != other.onLongClick) return false + if (onClickLabel != other.onClickLabel) return false + if (onClick != other.onClick) return false + + return true + } +} + +private class ClickableSemanticsNode( + var enabled: Boolean, + var role: Role?, + var onLongClickLabel: String?, + var onLongClick: (() -> Unit)?, + var onClickLabel: String?, + var onClick: () -> Unit, +) : SemanticsModifierNode, Modifier.Node() { + override val semanticsConfiguration + get() = SemanticsConfiguration().apply { + isMergingSemanticsOfDescendants = true + if (this@ClickableSemanticsNode.role != null) { + role = this@ClickableSemanticsNode.role!! + } + onClick( + action = { onClick(); true }, + label = onClickLabel + ) + if (onLongClick != null) { + onLongClick( + action = { onLongClick?.invoke(); true }, + label = onLongClickLabel + ) + } + if (!enabled) { + disabled() + } + } } \ No newline at end of file