1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.compose.foundation.interaction 18 19 import androidx.compose.runtime.Stable 20 import androidx.compose.runtime.State 21 import androidx.compose.runtime.annotation.RememberInComposition 22 import androidx.compose.runtime.remember 23 import kotlin.js.JsName 24 import kotlinx.coroutines.channels.BufferOverflow 25 import kotlinx.coroutines.flow.Flow 26 import kotlinx.coroutines.flow.MutableSharedFlow 27 28 /** 29 * InteractionSource represents a stream of [Interaction]s corresponding to events emitted by a 30 * component. These [Interaction]s can be used to change how components appear in different states, 31 * such as when a component is pressed or dragged. 32 * 33 * A common use case is [androidx.compose.foundation.indication], where 34 * [androidx.compose.foundation.Indication] implementations can subscribe to an [InteractionSource] 35 * to draw indication for different [Interaction]s, such as a ripple effect for 36 * [PressInteraction.Press] and a state overlay for [DragInteraction.Start]. 37 * 38 * For simple cases where you are interested in the binary state of an [Interaction], such as 39 * whether a component is pressed or not, you can use [InteractionSource.collectIsPressedAsState] 40 * and other extension functions that subscribe and return a [Boolean] [State] representing whether 41 * the component is in this state or not. 42 * 43 * @sample androidx.compose.foundation.samples.SimpleInteractionSourceSample 44 * 45 * For more complex cases, such as when building an [androidx.compose.foundation.Indication], the 46 * order of the events can change how a component / indication should be drawn. For example, if a 47 * component is being dragged and then becomes focused, the most recent [Interaction] is 48 * [FocusInteraction.Focus], so the component should appear in a focused state to signal this event 49 * to the user. 50 * 51 * InteractionSource exposes [interactions] to support these use cases - a [Flow] representing the 52 * stream of all emitted [Interaction]s. This also provides more information, such as the press 53 * position of [PressInteraction.Press], so you can show an effect at the specific point the 54 * component was pressed, and whether the press was [PressInteraction.Release] or 55 * [PressInteraction.Cancel], for cases when a component should behave differently if the press was 56 * released normally or interrupted by another gesture. 57 * 58 * You can collect from [interactions] as you would with any other [Flow]: 59 * 60 * @sample androidx.compose.foundation.samples.InteractionSourceFlowSample 61 * 62 * To emit [Interaction]s so that consumers can react to them, see [MutableInteractionSource]. 63 * 64 * @see MutableInteractionSource 65 * @see Interaction 66 */ 67 @Stable 68 interface InteractionSource { 69 /** 70 * [Flow] representing the stream of all [Interaction]s emitted through this 71 * [InteractionSource]. This can be used to see [Interaction]s emitted in order, and with 72 * additional metadata, such as the press position for [PressInteraction.Press]. 73 * 74 * @sample androidx.compose.foundation.samples.InteractionSourceFlowSample 75 */ 76 val interactions: Flow<Interaction> 77 } 78 79 /** 80 * MutableInteractionSource represents a stream of [Interaction]s corresponding to events emitted by 81 * a component. These [Interaction]s can be used to change how components appear in different 82 * states, such as when a component is pressed or dragged. 83 * 84 * Lower level interaction APIs such as [androidx.compose.foundation.clickable] and 85 * [androidx.compose.foundation.gestures.draggable] have an [MutableInteractionSource] parameter, 86 * which allows you to hoist an [MutableInteractionSource] and combine multiple interactions into 87 * one event stream. 88 * 89 * MutableInteractionSource exposes [emit] and [tryEmit] functions. These emit the provided 90 * [Interaction] to the underlying [interactions] [Flow], allowing consumers to react to these new 91 * [Interaction]s. 92 * 93 * An instance of MutableInteractionSource can be created by using the [MutableInteractionSource] 94 * factory function. This instance should be [remember]ed before it is passed to other components 95 * that consume it. 96 * 97 * @see InteractionSource 98 * @see Interaction 99 */ 100 @Stable 101 interface MutableInteractionSource : InteractionSource { 102 /** 103 * Emits [interaction] into [interactions]. This method is not thread-safe and should not be 104 * invoked concurrently. 105 * 106 * @see tryEmit 107 */ emitnull108 suspend fun emit(interaction: Interaction) 109 110 /** 111 * Tries to emit [interaction] into [interactions] without suspending. It returns `true` if the 112 * value was emitted successfully. 113 * 114 * @see emit 115 */ 116 fun tryEmit(interaction: Interaction): Boolean 117 } 118 119 /** 120 * Return a new [MutableInteractionSource] that can be hoisted and provided to components, allowing 121 * listening to [Interaction] changes inside those components. 122 * 123 * This should be [remember]ed before it is provided to components, so it can maintain its state 124 * across compositions. 125 * 126 * @see InteractionSource 127 * @see MutableInteractionSource 128 */ 129 @JsName("funMutableInteractionSource") 130 @RememberInComposition 131 fun MutableInteractionSource(): MutableInteractionSource = MutableInteractionSourceImpl() 132 133 @Stable 134 private class MutableInteractionSourceImpl : MutableInteractionSource { 135 // TODO: consider replay for new indication instances during events? 136 override val interactions = 137 MutableSharedFlow<Interaction>( 138 extraBufferCapacity = 16, 139 onBufferOverflow = BufferOverflow.DROP_OLDEST, 140 ) 141 142 override suspend fun emit(interaction: Interaction) { 143 interactions.emit(interaction) 144 } 145 146 override fun tryEmit(interaction: Interaction): Boolean { 147 return interactions.tryEmit(interaction) 148 } 149 } 150