diff --git a/designcompose/src/main/java/com/android/designcompose/CustomizationContext.kt b/designcompose/src/main/java/com/android/designcompose/CustomizationContext.kt index cfdfec4ed..29a1d07d8 100644 --- a/designcompose/src/main/java/com/android/designcompose/CustomizationContext.kt +++ b/designcompose/src/main/java/com/android/designcompose/CustomizationContext.kt @@ -426,11 +426,10 @@ fun CustomizationContext.getComponent( // and it does a lot of hashing and string operations. We can optimize this by parsing the node // variants and sorting them in Rust during doc generation, and by interning strings in the // serialized doc so that we don't need to hash. -fun CustomizationContext.getMatchingVariant(maybeComponentInfo: Optional): String? { - if (!maybeComponentInfo.isPresent) return null +fun CustomizationContext.getMatchingVariant(componentInfo: ComponentInfo?): String? { + if (componentInfo == null) return null if (variantProperties.isEmpty()) return null - val componentInfo = maybeComponentInfo.get() val nodeVariants = parseNodeVariants(componentInfo.name) // Check to see if any of the variant properties set match the variant properties in this node. diff --git a/designcompose/src/main/java/com/android/designcompose/proto/ProtoUtils.kt b/designcompose/src/main/java/com/android/designcompose/proto/ProtoUtils.kt index 2d0c0cf09..e485680a9 100644 --- a/designcompose/src/main/java/com/android/designcompose/proto/ProtoUtils.kt +++ b/designcompose/src/main/java/com/android/designcompose/proto/ProtoUtils.kt @@ -101,6 +101,7 @@ internal fun Optional.ifTextGetText(): Text? { return (this.get().view_data_type.get() as? ViewDataType.Text)?.value } +@Deprecated("This function will be removed in the future.") internal fun Optional.isSupportedInteraction() = this.type is TriggerType.AfterTimeout || this.type is TriggerType.Click || diff --git a/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshTreeBuilder.kt b/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshTreeBuilder.kt index 4cfbe1edd..52a33383b 100644 --- a/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshTreeBuilder.kt +++ b/designcompose/src/main/java/com/android/designcompose/squoosh/SquooshTreeBuilder.kt @@ -29,10 +29,35 @@ import com.android.designcompose.KeyAction import com.android.designcompose.KeyEventTracker import com.android.designcompose.VariableState import com.android.designcompose.common.NodeQuery -import com.android.designcompose.definition.layout.LayoutStyle -import com.android.designcompose.definition.view.NodeStyle +import com.android.designcompose.definition.element.ViewShapeKt.box +import com.android.designcompose.definition.element.background +import com.android.designcompose.definition.element.colorOrVar +import com.android.designcompose.definition.element.dimensionProto +import com.android.designcompose.definition.element.viewShape +import com.android.designcompose.definition.interaction.action +import com.android.designcompose.definition.interaction.reaction +import com.android.designcompose.definition.interaction.trigger +import com.android.designcompose.definition.layout.AlignItems +import com.android.designcompose.definition.layout.FlexDirection +import com.android.designcompose.definition.layout.JustifyContent +import com.android.designcompose.definition.layout.Overflow +import com.android.designcompose.definition.layout.OverflowDirection +import com.android.designcompose.definition.layout.PositionType +import com.android.designcompose.definition.layout.scrollInfo +import com.android.designcompose.definition.plugin.FrameExtras +import com.android.designcompose.definition.plugin.OverlayBackgroundInteraction +import com.android.designcompose.definition.plugin.OverlayPositionType +import com.android.designcompose.definition.plugin.colorOrNull +import com.android.designcompose.definition.plugin.overlayBackgroundOrNull +import com.android.designcompose.definition.view.ComponentInfo import com.android.designcompose.definition.view.View +import com.android.designcompose.definition.view.ViewDataKt.container import com.android.designcompose.definition.view.ViewStyle +import com.android.designcompose.definition.view.containerOrNull +import com.android.designcompose.definition.view.frameExtrasOrNull +import com.android.designcompose.definition.view.view +import com.android.designcompose.definition.view.viewData +import com.android.designcompose.definition.view.viewStyle import com.android.designcompose.getComponent import com.android.designcompose.getContent import com.android.designcompose.getKey @@ -41,47 +66,12 @@ import com.android.designcompose.getMatchingVariant import com.android.designcompose.getTapCallback import com.android.designcompose.getVisible import com.android.designcompose.getVisibleState -import com.android.designcompose.proto.OverlayBackgroundInteractionEnum -import com.android.designcompose.proto.OverlayPositionEnum -import com.android.designcompose.proto.getType -import com.android.designcompose.proto.isSupportedInteraction -import com.android.designcompose.proto.layoutStyle -import com.android.designcompose.proto.newDimensionProtoPercent -import com.android.designcompose.proto.newViewShapeRect -import com.android.designcompose.proto.nodeStyle -import com.android.designcompose.proto.overlayBackgroundInteractionFromInt -import com.android.designcompose.proto.overlayPositionEnumFromInt -import com.android.designcompose.proto.toInt -import com.android.designcompose.proto.type -import com.android.designcompose.serdegen.Action -import com.android.designcompose.serdegen.ActionType -import com.android.designcompose.serdegen.AlignItems -import com.android.designcompose.serdegen.Background -import com.android.designcompose.serdegen.BackgroundType -import com.android.designcompose.serdegen.Color -import com.android.designcompose.serdegen.ColorOrVar -import com.android.designcompose.serdegen.ColorOrVarType -import com.android.designcompose.serdegen.ComponentInfo -import com.android.designcompose.serdegen.Container -import com.android.designcompose.serdegen.FlexDirection -import com.android.designcompose.serdegen.FrameExtras -import com.android.designcompose.serdegen.JustifyContent -import com.android.designcompose.serdegen.Overflow -import com.android.designcompose.serdegen.OverflowDirection -import com.android.designcompose.serdegen.PositionType -import com.android.designcompose.serdegen.Reaction -import com.android.designcompose.serdegen.RenderMethod -import com.android.designcompose.serdegen.ScrollInfo -import com.android.designcompose.serdegen.Trigger -import com.android.designcompose.serdegen.TriggerType -import com.android.designcompose.serdegen.ViewData -import com.android.designcompose.serdegen.ViewDataType import com.android.designcompose.squooshNodeVariant import com.android.designcompose.squooshRootNode import com.android.designcompose.utils.hasScrolling +import com.android.designcompose.utils.isSupportedInteraction import com.android.designcompose.utils.mergeStyles -import java.util.Optional -import kotlin.jvm.optionals.getOrNull +import com.google.protobuf.empty // Remember if there's a child composable for a given node, and also we return an ordered // list of all the child composables we need to render, along with transforms etc. @@ -178,9 +168,8 @@ internal fun resolveVariantsRecursively( // If we have a component then we might need to get an override style, and we definitely // need to get a different layout id. - if (v.component_info.isPresent) { - val componentInfo = v.component_info.get() - parentComps = ParentComponentData(parentComponents, v.id, componentInfo) + if (v.hasComponentInfo()) { + parentComps = ParentComponentData(parentComponents, v.id, v.componentInfo) // Ensure that the children of this component get unique layout ids, even though there // may be multiple instances of the same component in one tree. @@ -192,10 +181,10 @@ internal fun resolveVariantsRecursively( // Do we have an override style? This is style data which we should apply to the final style // even if we're swapping out our view definition for a variant. - if (componentInfo.overrides.isPresent) { - val overrides = componentInfo.overrides.get() - if (overrides.style.isPresent) { - overrideStyle = overrides.style.get() + if (v.componentInfo.hasOverrides()) { + val overrides = v.componentInfo.overrides + if (overrides.hasStyle()) { + overrideStyle = overrides.style } } @@ -203,11 +192,13 @@ internal fun resolveVariantsRecursively( // interaction-driven and customization-driven variant changes). // First ask the interaction state if there's a variant we should render instead. - view = interactionState.squooshNodeVariant(v.id, customizations.getKey(), document) ?: v + interactionState.squooshNodeVariant(v.id, customizations.getKey(), document)?.let { + view = it + } // If we didn't replace the component because of an interaction, we might want to replace it // because of a variant customization. - if (view.name == v.name) { + if (view.name == v.name) { // TODO: why we don't check if view == v???? // If an interaction has not changed the current variant, then check to see if this node // is part of a component set with variants and if any @DesignVariant annotations // set variant properties that match. If so, variantNodeName will be set to the @@ -220,14 +211,14 @@ internal fun resolveVariantsRecursively( val variantNodeName = variantTransition.selectVariant( v.id, - customizations.getMatchingVariant(view.component_info) ?: componentInfo.name, + customizations.getMatchingVariant(v.componentInfo) ?: v.componentInfo.name, ) - if (variantNodeName != null && variantNodeName != componentInfo.name) { + if (variantNodeName != null && variantNodeName != v.componentInfo.name) { // Find the view associated with the variant name val variantNodeQuery = NodeQuery.NodeVariant( variantNodeName, - variantParentName.ifEmpty { view.component_info.get().component_set_name }, + variantParentName.ifEmpty { v.componentInfo.componentSetName }, ) val variantView = interactionState.squooshRootNode( @@ -248,17 +239,17 @@ internal fun resolveVariantsRecursively( // that on top of the view (or variant) style. val style = if (overrideStyle == null) { - view.style.get() + view.style } else { // XXX-PERF: This is not needed by Battleship, and takes over 50% of the runtime of // resolveVariants (not including computeTextInfo). - mergeStyles(view.style.get(), overrideStyle) + mergeStyles(view.style, overrideStyle) } // Now we know the view we want to render, the style we want to use, etc. We can create // a record of it. After this, another pass can be done to build a layout tree. Finally, // layout can be performed and rendering done. - val layoutId = computeLayoutId(componentLayoutId, v.unique_id) + val layoutId = computeLayoutId(componentLayoutId, v.uniqueId) layoutIdAllocator.visitLayoutId(layoutId) // XXX-PERF: computeTextInfo is *super* slow. It needs to use a cache between frames. @@ -280,17 +271,17 @@ internal fun resolveVariantsRecursively( // Find out if we have some supported interactions. These currently include press, click, // timeout, and key press. var hasSupportedInteraction = false - view.reactions.forEach { r -> + view.reactionsList.forEach { r -> hasSupportedInteraction = hasSupportedInteraction || r.trigger.isSupportedInteraction() - if (r.trigger.type is TriggerType.KeyDown) { + if (r.trigger.hasKeyDown()) { // Register to be a listener for key reactions on this node - val keyTrigger = r.trigger.type as TriggerType.KeyDown - val keyEvent = DesignKeyEvent.fromJsKeyCodes(keyTrigger.value.key_codes) + val keyTrigger = r.trigger.keyDown + val keyEvent = DesignKeyEvent.fromJsKeyCodes(keyTrigger.keyCodes.toList()) val keyAction = KeyAction( interactionState, - r.action.get(), - findTargetInstanceId(document, parentComps, r.action.get()), + r.action, + findTargetInstanceId(document, parentComps, r.action), customizations.getKey(), null, ) @@ -381,10 +372,10 @@ internal fun resolveVariantsRecursively( } if (!skipChildren) { - (view.data.getType() as? ViewDataType.Container)?.let { viewData -> + view.data.containerOrNull?.let { viewData -> var previousChild: SquooshResolvedNode? = null - for (child in viewData.value.children) { + for (child in view.data.container.childrenList) { val childResolvedNode = resolveVariantsRecursively( child, @@ -421,7 +412,7 @@ internal fun resolveVariantsRecursively( // Roots also get overlays if (overlays != null) { for (overlay in overlays) { - val overlayExtras = overlay.frame_extras.getOrNull() ?: continue + val overlayExtras = overlay.frameExtrasOrNull ?: continue // We want to ensure that the background close interaction appears after the regular // content but before the child content. We can't construct a `SquooshChildComposable` @@ -488,49 +479,21 @@ internal fun generateReplacementListChildNode( childIdx: Int, layoutIdAllocator: SquooshLayoutIdAllocator, ): SquooshResolvedNode { - val layoutStyle = LayoutStyle.newBuilder() - val nodeStyle = NodeStyle.newBuilder() - - val itemStyle = - ViewStyle.Builder() - .apply { - layout_style = Optional.of(layoutStyle.build()) - node_style = Optional.of(nodeStyle.build()) - } - .build() + val listChildViewData = container { shape = viewShape { rect = box { isMask = false } } } - val listChildViewData = - Container.Builder() - .apply { - shape = Optional.of(newViewShapeRect(false)) - children = emptyList() - } - .build() + val listChildScrollInfo = scrollInfo { + pagedScrolling = false + overflow = OverflowDirection.OVERFLOW_DIRECTION_NONE + } - val listChildScrollInfo = - ScrollInfo.Builder() - .apply { - paged_scrolling = false - overflow = OverflowDirection.None().toInt() - } - .build() - - val listChildView = View.Builder() - listChildView.unique_id = 0 // This is unused. - listChildView.id = "replacement-${node.view.id}-${childIdx}" - listChildView.name = "Replacement List ${node.view.name} / $childIdx" - listChildView.component_info = Optional.empty() - listChildView.reactions = emptyList() - listChildView.frame_extras = Optional.empty() - listChildView.shader = Optional.empty() - listChildView.shader_fallback_color = Optional.empty() - listChildView.scroll_info = Optional.of(listChildScrollInfo) - listChildView.style = Optional.of(itemStyle) - listChildView.data = - Optional.of(ViewData(Optional.of(ViewDataType.Container(listChildViewData)))) - listChildView.design_absolute_bounding_box = Optional.empty() - listChildView.render_method = RenderMethod.None().toInt() - listChildView.explicit_variable_modes = emptyMap() + val listChildView = view { + uniqueId = 0 // This is unused. + id = "replacement-${node.view.id}-${childIdx}" + name = "Replacement List ${node.view.name} / $childIdx" + scrollInfo = listChildScrollInfo + data = viewData { this.container = listChildViewData } + renderMethod = View.RenderMethod.RENDER_METHOD_NONE + } val listLayoutId = layoutIdAllocator.listLayoutId(node.layoutId) val layoutId = computeSyntheticListItemLayoutId(listLayoutId, childIdx) @@ -538,8 +501,8 @@ internal fun generateReplacementListChildNode( val listChildNode = SquooshResolvedNode( - view = listChildView.build(), - style = listChildView.style.get(), + view = listChildView, + style = listChildView.style, layoutId = layoutId, textInfo = null, unresolvedNodeId = "list-child-${node.unresolvedNodeId}-${childIdx}", @@ -561,121 +524,102 @@ private fun generateOverlayNode( layoutIdAllocator: SquooshLayoutIdAllocator, ): SquooshResolvedNode { // Make a view based on the child node which uses a synthesized style. - val overlayStyle = node.style.toBuilder() - val layoutStyle = node.style.layoutStyle.toBuilder() - val nodeStyle = node.style.nodeStyle.toBuilder() - nodeStyle.overflow = Overflow.Visible().toInt() - layoutStyle.position_type = PositionType.Absolute().toInt() - layoutStyle.top = newDimensionProtoPercent(0.0f) - layoutStyle.left = newDimensionProtoPercent(0.0f) - layoutStyle.right = newDimensionProtoPercent(0.0f) - layoutStyle.bottom = newDimensionProtoPercent(0.0f) - layoutStyle.width = newDimensionProtoPercent(1.0f) - layoutStyle.height = newDimensionProtoPercent(1.0f) - layoutStyle.flex_direction = FlexDirection.Column().toInt() - when (overlayPositionEnumFromInt(overlay.overlay_position_type)) { - OverlayPositionEnum.TOP_LEFT -> { - layoutStyle.justify_content = JustifyContent.FlexStart().toInt() // Y - layoutStyle.align_items = AlignItems.FlexStart().toInt() // X + val layoutStyleBuilder = node.style.layoutStyle.toBuilder() + layoutStyleBuilder.positionType = PositionType.POSITION_TYPE_ABSOLUTE + layoutStyleBuilder.top = dimensionProto { points = 0.0f } + layoutStyleBuilder.left = dimensionProto { points = 0.0f } + layoutStyleBuilder.right = dimensionProto { points = 0.0f } + layoutStyleBuilder.bottom = dimensionProto { points = 0.0f } + layoutStyleBuilder.width = dimensionProto { points = 0.0f } + layoutStyleBuilder.height = dimensionProto { points = 0.0f } + layoutStyleBuilder.flexDirection = FlexDirection.FLEX_DIRECTION_COLUMN + when (overlay.overlayPositionType) { + OverlayPositionType.OVERLAY_POSITION_TYPE_TOP_LEFT -> { + layoutStyleBuilder.justifyContent = JustifyContent.JUSTIFY_CONTENT_FLEX_START // Y + layoutStyleBuilder.alignItems = AlignItems.ALIGN_ITEMS_FLEX_START // X } - OverlayPositionEnum.TOP_CENTER -> { - layoutStyle.justify_content = JustifyContent.FlexStart().toInt() // Y - layoutStyle.align_items = AlignItems.Center().toInt() // X + OverlayPositionType.OVERLAY_POSITION_TYPE_TOP_CENTER -> { + layoutStyleBuilder.justifyContent = JustifyContent.JUSTIFY_CONTENT_FLEX_START // Y + layoutStyleBuilder.alignItems = AlignItems.ALIGN_ITEMS_CENTER // X } - OverlayPositionEnum.TOP_RIGHT -> { - layoutStyle.justify_content = JustifyContent.FlexStart().toInt() // Y - layoutStyle.align_items = AlignItems.FlexEnd().toInt() // X + OverlayPositionType.OVERLAY_POSITION_TYPE_TOP_RIGHT -> { + layoutStyleBuilder.justifyContent = JustifyContent.JUSTIFY_CONTENT_FLEX_START // Y + layoutStyleBuilder.alignItems = AlignItems.ALIGN_ITEMS_FLEX_END // X } - OverlayPositionEnum.BOTTOM_LEFT -> { - layoutStyle.justify_content = JustifyContent.FlexEnd().toInt() // Y - layoutStyle.align_items = AlignItems.FlexStart().toInt() // X + OverlayPositionType.OVERLAY_POSITION_TYPE_BOTTOM_LEFT -> { + layoutStyleBuilder.justifyContent = JustifyContent.JUSTIFY_CONTENT_FLEX_END // Y + layoutStyleBuilder.alignItems = AlignItems.ALIGN_ITEMS_FLEX_START // X } - OverlayPositionEnum.BOTTOM_CENTER -> { - layoutStyle.justify_content = JustifyContent.FlexEnd().toInt() // Y - layoutStyle.align_items = AlignItems.Center().toInt() // X + OverlayPositionType.OVERLAY_POSITION_TYPE_BOTTOM_CENTER -> { + layoutStyleBuilder.justifyContent = JustifyContent.JUSTIFY_CONTENT_FLEX_END // Y + layoutStyleBuilder.alignItems = AlignItems.ALIGN_ITEMS_CENTER // X } - OverlayPositionEnum.BOTTOM_RIGHT -> { - layoutStyle.justify_content = JustifyContent.FlexEnd().toInt() // Y - layoutStyle.align_items = AlignItems.FlexEnd().toInt() // X + OverlayPositionType.OVERLAY_POSITION_TYPE_BOTTOM_RIGHT -> { + layoutStyleBuilder.justifyContent = JustifyContent.JUSTIFY_CONTENT_FLEX_END // Y + layoutStyleBuilder.alignItems = AlignItems.ALIGN_ITEMS_FLEX_END // X } // Center and Manual both are centered; not clear how to implement manual positioning // without making a layout-dependent query. else -> { - layoutStyle.justify_content = JustifyContent.Center().toInt() - layoutStyle.align_items = AlignItems.Center().toInt() + layoutStyleBuilder.justifyContent = JustifyContent.JUSTIFY_CONTENT_CENTER // Y + layoutStyleBuilder.alignItems = AlignItems.ALIGN_ITEMS_CENTER // X } } - nodeStyle.backgrounds = listOf() - - overlay.overlay_background.getOrNull()?.color?.getOrNull()?.let { color -> - val colorBuilder = Color.Builder() - colorBuilder.r = (color.r * 255.0).toInt() - colorBuilder.g = (color.g * 255.0).toInt() - colorBuilder.b = (color.b * 255.0).toInt() - colorBuilder.a = (color.a * 255.0).toInt() - nodeStyle.backgrounds = - listOf( - Background( - Optional.of( - BackgroundType.Solid( - ColorOrVar(Optional.of(ColorOrVarType.Color(colorBuilder.build()))) - ) - ) - ) - ) + + val nodeStyleBuilder = node.style.nodeStyle.toBuilder() + nodeStyleBuilder.overflow = Overflow.OVERFLOW_VISIBLE + nodeStyleBuilder.backgroundsList.clear() + + overlay.overlayBackgroundOrNull?.colorOrNull?.let { + val bgColor = + com.android.designcompose.definition.element.color { + r = (it.r * 255.0).toInt() + g = (it.g * 255.0).toInt() + b = (it.b * 255.0).toInt() + a = (it.a * 255.0).toInt() + } + nodeStyleBuilder.backgroundsList.add(background { solid = colorOrVar { color = bgColor } }) + } + + val overlayStyle = viewStyle { + layoutStyle = layoutStyleBuilder.build() + nodeStyle = nodeStyleBuilder.build() } - overlayStyle.layout_style = Optional.of(layoutStyle.build()) - overlayStyle.node_style = Optional.of(nodeStyle.build()) - val style = overlayStyle.build() // Now synthesize a view. - val overlayViewData = - Container.Builder() - .apply { - shape = Optional.of(newViewShapeRect(false)) - children = listOf(node.view) - } - .build() + val overlayViewData = container { + shape = viewShape { rect = box { isMask = false } } + children.add(node.view) + } - val overlayScrollInfo = - ScrollInfo.Builder() - .apply { - paged_scrolling = false - overflow = OverflowDirection.None().toInt() - } - .build() + val overlayScrollInfo = scrollInfo { + pagedScrolling = false + overflow = OverflowDirection.OVERFLOW_DIRECTION_NONE + } - val overlayView = View.Builder() - if (node.view.unique_id !in 0..0xFFFF) { + if (node.view.uniqueId !in 0..0xFFFF) { throw RuntimeException("View's unique ID must be in the range 0..0xFFFF") } - overlayView.unique_id = (node.view.unique_id + 0x2000) - overlayView.id = "overlay-${node.view.id}" - overlayView.name = "Overlay ${node.view.name}" - overlayView.component_info = Optional.empty() - overlayView.reactions = + val overlayView = view { + uniqueId = (node.view.uniqueId + 0x2000) + id = "overlay-${node.view.id}" + name = "Overlay ${node.view.name}" if ( - overlayBackgroundInteractionFromInt(overlay.overlay_background_interaction) == - OverlayBackgroundInteractionEnum.CLOSE_ON_CLICK_OUTSIDE + overlay.overlayBackgroundInteraction == + OverlayBackgroundInteraction.OVERLAY_BACKGROUND_INTERACTION_CLOSE_ON_CLICK_OUTSIDE ) { - // Synthesize a reaction that will close the overlay. - val r = Reaction.Builder() - r.trigger = Optional.of(Trigger(Optional.of(TriggerType.Click(com.novi.serde.Unit())))) - r.action = Optional.of(Action(Optional.of(ActionType.Close(com.novi.serde.Unit())))) - listOf(r.build()) - } else { - emptyList() + reactions.add( + reaction { + trigger = trigger { click = empty {} } + action = action { close = empty {} } + } + ) } - overlayView.frame_extras = Optional.empty() - overlayView.shader = Optional.empty() - overlayView.shader_fallback_color = Optional.empty() - overlayView.scroll_info = Optional.of(overlayScrollInfo) - overlayView.style = Optional.of(style) - overlayView.data = Optional.of(ViewData(Optional.of(ViewDataType.Container(overlayViewData)))) - overlayView.design_absolute_bounding_box = Optional.empty() - overlayView.render_method = RenderMethod.None().toInt() - overlayView.explicit_variable_modes = emptyMap() - + scrollInfo = overlayScrollInfo + style = overlayStyle + data = viewData { container = overlayViewData } + renderMethod = View.RenderMethod.RENDER_METHOD_NONE + } val overlayLayoutId = layoutIdAllocator.listLayoutId(node.layoutId) val layoutId = computeSyntheticOverlayLayoutId( @@ -686,8 +630,8 @@ private fun generateOverlayNode( val overlayNode = SquooshResolvedNode( - view = overlayView.build(), - style = style, + view = overlayView, + style = overlayStyle, layoutId = layoutId, textInfo = null, unresolvedNodeId = "overlay-${node.unresolvedNodeId}", diff --git a/designcompose/src/main/java/com/android/designcompose/utils/InteractionUtils.kt b/designcompose/src/main/java/com/android/designcompose/utils/InteractionUtils.kt new file mode 100644 index 000000000..06b4f5beb --- /dev/null +++ b/designcompose/src/main/java/com/android/designcompose/utils/InteractionUtils.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.designcompose.utils + +import com.android.designcompose.definition.interaction.Trigger + +internal fun Trigger.isSupportedInteraction() = hasPress() || hasClick() || hasAfterTimeout()