| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * 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 androidx.compose.foundation |
| |
| import androidx.compose.Composable |
| import androidx.compose.CompositionLifecycleObserver |
| import androidx.compose.Stable |
| import androidx.compose.remember |
| import androidx.compose.staticAmbientOf |
| import androidx.ui.core.ContentDrawScope |
| import androidx.ui.core.DrawModifier |
| import androidx.ui.core.Modifier |
| import androidx.ui.core.composed |
| import androidx.compose.ui.graphics.Color |
| |
| /** |
| * Generic interface to define visual effects when certain interaction happens. Examples might |
| * be showing some press indication, such as material ripples or define custom decoration when |
| * item is dragged. |
| * |
| * This interface is factory-like and required to produce [IndicationInstance] on demand for |
| * [indication] modifier. |
| * |
| * If you want to override default behaviour for [indication] for the whole subtree, consider |
| * creating object of this factory and providing it in [IndicationAmbient]. |
| */ |
| @Stable |
| interface Indication { |
| |
| /** |
| * Function to create new [IndicationInstance] on demand. Typically this will be called by |
| * [indication] modified to spawn new instances when added to modified element. |
| */ |
| fun createInstance(): IndicationInstance |
| } |
| |
| /** |
| * Generic interface to define the instance if the [Indication] to draw visual effects when certain |
| * interaction happens. |
| * |
| * Indication can be stateful or stateless, and they expected to be created by [Indication] and |
| * used in-place and not reused between different [indication] modifiers. |
| */ |
| interface IndicationInstance { |
| |
| /** |
| * Method to draw visual effects based on [InteractionState]. |
| * |
| * Usually, in this method indication reads [InteractionState] to observe its value and draw |
| * any visuals to reflect this state. Refer to the [Interaction] to see what states are |
| * possible and draw visual effects when [InteractionState] contains them. |
| * |
| * This method MUST call [ContentDrawScope.drawContent] at some point in order to draw the |
| * rest of the UI tree below indication. |
| * |
| * @param interactionState state of the parent of this indication |
| */ |
| fun ContentDrawScope.drawIndication(interactionState: InteractionState) |
| |
| /** |
| * Callback which is called when this [IndicationInstance] disappears |
| * from composition and should free any allocated resources / stop on-going animations / etc |
| */ |
| fun onDispose() {} |
| } |
| |
| /** |
| * Show visual indicator for an [InteractionState]. |
| * |
| * @sample androidx.compose.foundation.samples.IndicationSample |
| * |
| * @param interactionState state for indication to indicate against. This state is updates by |
| * modifier such as [Clickable]. |
| * @param indication indication to be drawn. If `null`, there will be no indication shown |
| */ |
| fun Modifier.indication( |
| interactionState: InteractionState, |
| indication: Indication? = null |
| ) = composed { |
| val resolvedIndication = indication ?: NoIndication |
| remember(interactionState, resolvedIndication) { |
| IndicationModifier(interactionState, resolvedIndication.createInstance()) |
| } |
| } |
| |
| /** |
| * Ambient to provide [IndicationInstance] to draw visual indication for press and other events. |
| * |
| * By default there will be [DefaultDebugIndication] created. |
| */ |
| // TODO : temporary made it to be lambda, fix when b/157150564 is fixed |
| val IndicationAmbient = staticAmbientOf<@Composable () -> Indication> { { DefaultDebugIndication } } |
| |
| private object NoIndication : Indication { |
| private object NoIndicationInstance : IndicationInstance { |
| override fun ContentDrawScope.drawIndication(interactionState: InteractionState) { |
| drawContent() |
| } |
| } |
| |
| override fun createInstance(): IndicationInstance = NoIndicationInstance |
| } |
| |
| /** |
| * Simple default [Indication] that show visual effect when tap occurs. |
| */ |
| private object DefaultDebugIndication : Indication { |
| |
| private object DefaultDebugIndicationInstance : IndicationInstance { |
| override fun ContentDrawScope.drawIndication(interactionState: InteractionState) { |
| drawContent() |
| if (interactionState.contains(Interaction.Pressed)) { |
| drawRect(color = Color.Black.copy(alpha = 0.3f), size = size) |
| } |
| } |
| } |
| |
| override fun createInstance(): IndicationInstance { |
| return DefaultDebugIndicationInstance |
| } |
| } |
| |
| private class IndicationModifier( |
| val interactionState: InteractionState, |
| val indicationInstance: IndicationInstance |
| ) : CompositionLifecycleObserver, DrawModifier { |
| |
| override fun ContentDrawScope.draw() { |
| with(indicationInstance) { |
| drawIndication(interactionState) |
| } |
| } |
| |
| override fun onEnter() {} |
| |
| override fun onLeave() { |
| indicationInstance.onDispose() |
| } |
| } |