diff options
| author | 2024-09-16 17:45:06 +0000 | |
|---|---|---|
| committer | 2024-09-16 17:45:06 +0000 | |
| commit | c3b59309957f1b25d2709f7c5e05cdeca57a4cc5 (patch) | |
| tree | 9b0e343f744361d89a7a915f96ba9aca1313a1e6 | |
| parent | 6214c4ea88b9088c5d582b90467a4ececd34805a (diff) | |
| parent | 579da54f0936d3c802d228eec8e74939a9627604 (diff) | |
Merge "Implemented Interaction states for Shortcut Helper" into main
2 files changed, 170 insertions, 44 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt index 11a0543d97d1..4bf552e0f1e3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt @@ -286,7 +286,7 @@ private fun CategoryItemSinglePane( Column { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp) + modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp), ) { ShortcutCategoryIcon(modifier = Modifier.size(24.dp), source = category.icon) Spacer(modifier = Modifier.width(16.dp)) @@ -717,25 +717,24 @@ private fun CategoryItemTwoPane( colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent), ) { - val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() - SelectableShortcutSurface( selected = selected, onClick = onClick, - modifier = - Modifier.semantics { role = Role.Tab } - .heightIn(min = 64.dp) - .fillMaxWidth() - .outlineFocusModifier( - isFocused = isFocused, - focusColor = MaterialTheme.colorScheme.secondary, - padding = 2.dp, - cornerRadius = 33.dp, - ), + modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 64.dp).fillMaxWidth(), shape = RoundedCornerShape(28.dp), color = colors.containerColor(selected).value, - interactionSource = interactionSource + interactionsConfig = + InteractionsConfig( + hoverOverlayColor = MaterialTheme.colorScheme.onSurface, + hoverOverlayAlpha = 0.11f, + pressedOverlayColor = MaterialTheme.colorScheme.onSurface, + pressedOverlayAlpha = 0.15f, + focusOutlineColor = MaterialTheme.colorScheme.secondary, + focusOutlineStrokeWidth = 3.dp, + focusOutlinePadding = 2.dp, + surfaceCornerRadius = 28.dp, + focusOutlineCornerRadius = 33.dp, + ), ) { Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) { ShortcutCategoryIcon( @@ -843,25 +842,30 @@ private fun ShortcutsSearchBar(onQueryChange: (String) -> Unit) { @Composable private fun KeyboardSettings(horizontalPadding: Dp, verticalPadding: Dp, onClick: () -> Unit) { val interactionSource = remember { MutableInteractionSource() } - val isFocused by interactionSource.collectIsFocusedAsState() ClickableShortcutSurface( onClick = onClick, shape = RoundedCornerShape(24.dp), color = Color.Transparent, - modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth(), - interactionSource = interactionSource + modifier = + Modifier.semantics { role = Role.Button } + .fillMaxWidth() + .padding(horizontal = 12.dp), + interactionSource = interactionSource, + interactionsConfig = + InteractionsConfig( + hoverOverlayColor = MaterialTheme.colorScheme.onSurface, + hoverOverlayAlpha = 0.11f, + pressedOverlayColor = MaterialTheme.colorScheme.onSurface, + pressedOverlayAlpha = 0.15f, + focusOutlineColor = MaterialTheme.colorScheme.secondary, + focusOutlinePadding = 8.dp, + focusOutlineStrokeWidth = 3.dp, + surfaceCornerRadius = 24.dp, + focusOutlineCornerRadius = 28.dp, + hoverPadding = 8.dp, + ), ) { - Row( - modifier = - Modifier.padding(horizontal = 12.dp, vertical = 16.dp) - .outlineFocusModifier( - isFocused = isFocused, - focusColor = MaterialTheme.colorScheme.secondary, - padding = 8.dp, - cornerRadius = 28.dp, - ), - verticalAlignment = Alignment.CenterVertically, - ) { + Row(verticalAlignment = Alignment.CenterVertically) { Text( "Keyboard Settings", color = MaterialTheme.colorScheme.onSurfaceVariant, diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt index 3ba3bd8fdc2f..e49ce6062be3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/Surfaces.kt @@ -17,10 +17,16 @@ package com.android.systemui.keyboard.shortcut.ui.composable import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.IndicationNodeFactory import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.FocusInteraction +import androidx.compose.foundation.interaction.HoverInteraction +import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.PressInteraction +import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.selection.selectable import androidx.compose.material3.ColorScheme @@ -35,17 +41,27 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.NonRestartableComposable import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.drawscope.ContentDrawScope +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.node.DelegatableNode +import androidx.compose.ui.node.DrawModifierNode import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex import com.android.compose.modifiers.thenIf +import kotlinx.coroutines.launch /** * A selectable surface with no default focus/hover indications. @@ -67,15 +83,17 @@ fun SelectableShortcutSurface( shadowElevation: Dp = 0.dp, border: BorderStroke? = null, interactionSource: MutableInteractionSource? = null, - content: @Composable () -> Unit + interactionsConfig: InteractionsConfig = InteractionsConfig(), + content: @Composable () -> Unit, ) { @Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation CompositionLocalProvider( LocalContentColor provides contentColor, - LocalAbsoluteTonalElevation provides absoluteElevation + LocalAbsoluteTonalElevation provides absoluteElevation, ) { + val isFocused = interactionSource.collectIsFocusedAsState() Box( modifier = modifier @@ -85,16 +103,18 @@ fun SelectableShortcutSurface( backgroundColor = surfaceColorAtElevation(color = color, elevation = absoluteElevation), border = border, - shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() } + shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }, ) .selectable( selected = selected, interactionSource = interactionSource, - indication = null, + indication = + ShortcutHelperIndication(interactionSource, interactionsConfig), enabled = enabled, - onClick = onClick - ), - propagateMinConstraints = true + onClick = onClick, + ) + .thenIf(isFocused.value) { Modifier.zIndex(1f) }, + propagateMinConstraints = true, ) { content() } @@ -120,14 +140,15 @@ fun ClickableShortcutSurface( shadowElevation: Dp = 0.dp, border: BorderStroke? = null, interactionSource: MutableInteractionSource? = null, - content: @Composable () -> Unit + interactionsConfig: InteractionsConfig = InteractionsConfig(), + content: @Composable () -> Unit, ) { @Suppress("NAME_SHADOWING") val interactionSource = interactionSource ?: remember { MutableInteractionSource() } val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation CompositionLocalProvider( LocalContentColor provides contentColor, - LocalAbsoluteTonalElevation provides absoluteElevation + LocalAbsoluteTonalElevation provides absoluteElevation, ) { Box( modifier = @@ -138,15 +159,16 @@ fun ClickableShortcutSurface( backgroundColor = surfaceColorAtElevation(color = color, elevation = absoluteElevation), border = border, - shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() } + shadowElevation = with(LocalDensity.current) { shadowElevation.toPx() }, ) .clickable( interactionSource = interactionSource, - indication = null, + indication = + ShortcutHelperIndication(interactionSource, interactionsConfig), enabled = enabled, - onClick = onClick + onClick = onClick, ), - propagateMinConstraints = true + propagateMinConstraints = true, ) { content() } @@ -195,5 +217,105 @@ private fun Modifier.surface( } .thenIf(border != null) { Modifier.border(border!!, shape) } .background(color = backgroundColor, shape = shape) - .clip(shape) } + +private class ShortcutHelperInteractionsNode( + private val interactionSource: InteractionSource, + private val interactionsConfig: InteractionsConfig, +) : Modifier.Node(), DrawModifierNode { + + var isFocused = mutableStateOf(false) + var isHovered = mutableStateOf(false) + var isPressed = mutableStateOf(false) + + override fun onAttach() { + coroutineScope.launch { + val hoverInteractions = mutableListOf<HoverInteraction.Enter>() + val focusInteractions = mutableListOf<FocusInteraction.Focus>() + val pressInteractions = mutableListOf<PressInteraction.Press>() + + interactionSource.interactions.collect { interaction -> + when (interaction) { + is FocusInteraction.Focus -> focusInteractions.add(interaction) + is FocusInteraction.Unfocus -> focusInteractions.remove(interaction.focus) + is HoverInteraction.Enter -> hoverInteractions.add(interaction) + is HoverInteraction.Exit -> hoverInteractions.remove(interaction.enter) + is PressInteraction.Press -> pressInteractions.add(interaction) + is PressInteraction.Release -> pressInteractions.remove(interaction.press) + is PressInteraction.Cancel -> pressInteractions.add(interaction.press) + } + isHovered.value = hoverInteractions.isNotEmpty() + isPressed.value = pressInteractions.isNotEmpty() + isFocused.value = focusInteractions.isNotEmpty() + } + } + } + + override fun ContentDrawScope.draw() { + + fun getRectangleWithPadding(padding: Dp, size: Size): Rect { + return Rect(Offset.Zero, size).let { + if (interactionsConfig.focusOutlinePadding > 0.dp) { + it.inflate(padding.toPx()) + } else { + it.deflate(padding.unaryMinus().toPx()) + } + } + } + + drawContent() + if (isHovered.value) { + val hoverRect = getRectangleWithPadding(interactionsConfig.pressedPadding, size) + drawRoundRect( + color = interactionsConfig.hoverOverlayColor, + alpha = interactionsConfig.hoverOverlayAlpha, + cornerRadius = CornerRadius(interactionsConfig.surfaceCornerRadius.toPx()), + topLeft = hoverRect.topLeft, + size = hoverRect.size, + ) + } + if (isPressed.value) { + val pressedRect = getRectangleWithPadding(interactionsConfig.pressedPadding, size) + drawRoundRect( + color = interactionsConfig.pressedOverlayColor, + alpha = interactionsConfig.pressedOverlayAlpha, + cornerRadius = CornerRadius(interactionsConfig.surfaceCornerRadius.toPx()), + topLeft = pressedRect.topLeft, + size = pressedRect.size, + ) + } + if (isFocused.value) { + val focusOutline = getRectangleWithPadding(interactionsConfig.focusOutlinePadding, size) + drawRoundRect( + color = interactionsConfig.focusOutlineColor, + style = Stroke(width = interactionsConfig.focusOutlineStrokeWidth.toPx()), + topLeft = focusOutline.topLeft, + size = focusOutline.size, + cornerRadius = CornerRadius(interactionsConfig.focusOutlineCornerRadius.toPx()), + ) + } + } +} + +data class ShortcutHelperIndication( + private val interactionSource: InteractionSource, + private val interactionsConfig: InteractionsConfig, +) : IndicationNodeFactory { + override fun create(interactionSource: InteractionSource): DelegatableNode { + return ShortcutHelperInteractionsNode(interactionSource, interactionsConfig) + } +} + +data class InteractionsConfig( + val hoverOverlayColor: Color = Color.Transparent, + val hoverOverlayAlpha: Float = 0.0f, + val pressedOverlayColor: Color = Color.Transparent, + val pressedOverlayAlpha: Float = 0.0f, + val focusOutlineColor: Color = Color.Transparent, + val focusOutlineStrokeWidth: Dp = 0.dp, + val focusOutlinePadding: Dp = 0.dp, + val surfaceCornerRadius: Dp = 0.dp, + val focusOutlineCornerRadius: Dp = 0.dp, + val hoverPadding: Dp = 0.dp, + val pressedPadding: Dp = hoverPadding, +) |