[go: nahoru, domu]

blob: 96c5ba233e42e00f9751fdd770c3a3736eb8df56 [file] [log] [blame]
/*
* Copyright 2018 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.selection
import androidx.compose.foundation.Indication
import androidx.compose.foundation.Interaction
import androidx.compose.foundation.InteractionState
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.Strings
import androidx.compose.foundation.indication
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.gesture.pressIndicatorGestureFilter
import androidx.compose.ui.gesture.tapGestureFilter
import androidx.compose.ui.platform.debugInspectorInfo
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.semantics.disabled
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.role
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.semantics.toggleableState
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.state.ToggleableState.Indeterminate
import androidx.compose.ui.state.ToggleableState.Off
import androidx.compose.ui.state.ToggleableState.On
/**
* Configure component to make it toggleable via input and accessibility events
*
* This version has no [InteractionState] or [Indication] parameters, default indication from
* [LocalIndication] will be used. To specify [InteractionState] or [Indication], use another
* overload.
*
* @sample androidx.compose.foundation.samples.ToggleableSample
*
* @see [Modifier.triStateToggleable] if you require support for an indeterminate state.
*
* @param value whether Toggleable is on or off
* @param enabled whether or not this [toggleable] will handle input events and appear
* enabled for semantics purposes
* @param role the type of user interface element. Accessibility services might use this
* to describe the element or do customizations
* @param onValueChange callback to be invoked when toggleable is clicked,
* therefore the change of the state in requested.
*/
fun Modifier.toggleable(
value: Boolean,
enabled: Boolean = true,
role: Role? = null,
onValueChange: (Boolean) -> Unit
) = composed(
inspectorInfo = debugInspectorInfo {
name = "toggleable"
properties["value"] = value
properties["enabled"] = enabled
properties["role"] = role
properties["onValueChange"] = onValueChange
}
) {
toggleableImpl(
state = ToggleableState(value),
onClick = { onValueChange(!value) },
enabled = enabled,
role = role,
interactionState = remember { InteractionState() },
indication = LocalIndication.current()
)
}
/**
* Configure component to make it toggleable via input and accessibility events.
*
* This version requires both [InteractionState] and [Indication] to work properly. Use another
* overload if you don't need these parameters.
*
* @sample androidx.compose.foundation.samples.ToggleableSample
*
* @see [Modifier.triStateToggleable] if you require support for an indeterminate state.
*
* @param value whether Toggleable is on or off
* @param interactionState [InteractionState] that will be updated when this toggleable is
* pressed, using [Interaction.Pressed]
* @param indication indication to be shown when modified element is pressed. Be default,
* indication from [LocalIndication] will be used. Pass `null` to show no indication, or
* current value from [LocalIndication] to show theme default
* @param enabled whether or not this [toggleable] will handle input events and appear
* enabled for semantics purposes
* * @param role the type of user interface element. Accessibility services might use this
* to describe the element or do customizations
* @param onValueChange callback to be invoked when toggleable is clicked,
* therefore the change of the state in requested.
*/
fun Modifier.toggleable(
value: Boolean,
interactionState: InteractionState,
indication: Indication?,
enabled: Boolean = true,
role: Role? = null,
onValueChange: (Boolean) -> Unit
) = composed(
inspectorInfo = debugInspectorInfo {
name = "toggleable"
properties["value"] = value
properties["enabled"] = enabled
properties["role"] = role
properties["interactionState"] = interactionState
properties["indication"] = indication
properties["onValueChange"] = onValueChange
},
factory = {
toggleableImpl(
state = ToggleableState(value),
onClick = { onValueChange(!value) },
enabled = enabled,
role = role,
interactionState = interactionState,
indication = indication
)
}
)
/**
* Configure component to make it toggleable via input and accessibility events with three
* states: On, Off and Indeterminate.
*
* TriStateToggleable should be used when there are dependent Toggleables associated to this
* component and those can have different values.
*
* This version has no [InteractionState] or [Indication] parameters, default indication from
* [LocalIndication] will be used. To specify [InteractionState] or [Indication], use another
* overload.
*
* @sample androidx.compose.foundation.samples.TriStateToggleableSample
*
* @see [Modifier.toggleable] if you want to support only two states: on and off
*
* @param state current value for the component
* @param enabled whether or not this [triStateToggleable] will handle input events and
* appear enabled for semantics purposes
* @param role the type of user interface element. Accessibility services might use this
* to describe the element or do customizations
* @param onClick will be called when user clicks the toggleable.
*/
fun Modifier.triStateToggleable(
state: ToggleableState,
enabled: Boolean = true,
role: Role? = null,
onClick: () -> Unit
) = composed(
inspectorInfo = debugInspectorInfo {
name = "triStateToggleable"
properties["state"] = state
properties["enabled"] = enabled
properties["role"] = role
properties["onClick"] = onClick
}
) {
toggleableImpl(
state,
enabled,
role,
remember { InteractionState() },
LocalIndication.current(),
onClick
)
}
/**
* Configure component to make it toggleable via input and accessibility events with three
* states: On, Off and Indeterminate.
*
* TriStateToggleable should be used when there are dependent Toggleables associated to this
* component and those can have different values.
*
* This version requires both [InteractionState] and [Indication] to work properly. Use another
* overload if you don't need these parameters.
*
* @sample androidx.compose.foundation.samples.TriStateToggleableSample
*
* @see [Modifier.toggleable] if you want to support only two states: on and off
*
* @param state current value for the component
* @param interactionState [InteractionState] that will be updated when this toggleable is
* pressed, using [Interaction.Pressed]
* @param indication indication to be shown when modified element is pressed. Be default,
* indication from [LocalIndication] will be used. Pass `null` to show no indication, or
* current value from [LocalIndication] to show theme default
* @param enabled whether or not this [triStateToggleable] will handle input events and
* appear enabled for semantics purposes
* @param role the type of user interface element. Accessibility services might use this
* to describe the element or do customizations
* @param onClick will be called when user clicks the toggleable.
*/
fun Modifier.triStateToggleable(
state: ToggleableState,
interactionState: InteractionState,
indication: Indication?,
enabled: Boolean = true,
role: Role? = null,
onClick: () -> Unit
) = composed(
inspectorInfo = debugInspectorInfo {
name = "triStateToggleable"
properties["state"] = state
properties["enabled"] = enabled
properties["role"] = role
properties["interactionState"] = interactionState
properties["indication"] = indication
properties["onClick"] = onClick
},
factory = {
toggleableImpl(state, enabled, role, interactionState, indication, onClick)
}
)
@Suppress("ModifierInspectorInfo", "DEPRECATION")
private fun Modifier.toggleableImpl(
state: ToggleableState,
enabled: Boolean,
role: Role? = null,
interactionState: InteractionState,
indication: Indication?,
onClick: () -> Unit
): Modifier = composed {
// TODO(pavlis): Handle multiple states for Semantics
val semantics = Modifier.semantics(mergeDescendants = true) {
if (role != null) {
this.role = role
}
this.stateDescription = when (state) {
On -> if (role == Role.Switch) Strings.On else Strings.Checked
Off -> if (role == Role.Switch) Strings.Off else Strings.Unchecked
Indeterminate -> Strings.Indeterminate
}
this.toggleableState = state
onClick(action = { onClick(); return@onClick true }, label = Strings.Toggle)
if (!enabled) {
disabled()
}
}
val interactionUpdate =
if (enabled) {
Modifier.pressIndicatorGestureFilter(
onStart = { interactionState.addInteraction(Interaction.Pressed, it) },
onStop = { interactionState.removeInteraction(Interaction.Pressed) },
onCancel = { interactionState.removeInteraction(Interaction.Pressed) }
)
} else {
Modifier
}
val click = if (enabled) Modifier.tapGestureFilter { onClick() } else Modifier
DisposableEffect(interactionState) {
onDispose {
interactionState.removeInteraction(Interaction.Pressed)
}
}
this
.then(semantics)
.indication(interactionState, indication)
.then(interactionUpdate)
.then(click)
}