[go: nahoru, domu]

blob: 4f6baea51eecac4ecc5e711377bf00e835c3e98a [file] [log] [blame]
/*
* Copyright 2019 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.ui.framework.demos
import androidx.ui.layout.Row
import androidx.ui.core.PxPosition
import androidx.ui.core.Text
import androidx.ui.core.dp
import androidx.ui.core.gesture.PressGestureDetector
import androidx.ui.core.px
import androidx.ui.layout.Column
import androidx.ui.layout.Container
import androidx.ui.material.Button
import androidx.ui.material.MaterialTheme
import androidx.compose.Composable
import androidx.compose.state
import androidx.compose.unaryPlus
import androidx.ui.layout.Arrangement
import androidx.ui.layout.LayoutExpandedHeight
import androidx.ui.layout.LayoutExpandedWidth
import androidx.ui.layout.LayoutGravity
/** A [SemanticProperty] is used to store semantic information about a component.
*
* Note: We use an interface instead of a sealed class here to allow SemanticProperty to be
* implemented as an Enum (Enums cannot inherit from classes, but they can implement interfaces). */
interface SemanticProperty<T> {
val value: T
// Returning null signifies that this property cannot be merged.
fun merge(other: SemanticProperty<T>): SemanticProperty<T>? = null
}
// These are some example SemanticProperties provided by the framework.
/** [Label] stores a string that describes the component. */
class Label(override var value: String) : SemanticProperty<String> {
// Labels are concatenated.
override fun merge(other: SemanticProperty<String>) = Label("$this $other")
}
/** [Visibility] stores an enum that represents the visibility of the component. */
enum class Visibility : SemanticProperty<Visibility> {
@Suppress("Unused")
Undefined,
Visible,
@Suppress("Unused")
Invisible;
override val value: Visibility get() = this
// The visibility of the parent takes precedence.
override fun merge(other: SemanticProperty<Visibility>) = this
}
/**
* This class provides a way to store a function that is run when the [SemanticAction] is invoked.
*/
class SemanticAction<T>(
val phrase: String = "",
val defaultParam: T,
val types: Set<ActionType> = setOf(),
val action: (ActionParam<T>) -> Unit
)
/**
* An extension function to invoke the action.
*/
fun <T> SemanticAction<T>.invoke(
caller: ActionCaller = ActionCaller.Unknown,
param: T = defaultParam
) = action(ActionParam(caller, param))
/**
* The parameter sent to every callback. In addition to the parameter value, it also provides
* information about the framework that raised the action.
*/
class ActionParam<T>(
@Suppress("Unused") val caller: ActionCaller = ActionCaller.Unknown,
val value: T
)
/**
* Frameworks that invoke the action. The developer might be interested in knowing which framework
* invoked the action.
*/
enum class ActionCaller {
Unknown,
Accessibility,
@Suppress("Unused")
AutoFill,
Assistant,
PointerInput,
@Suppress("Unused")
KeyInput
}
/**
* The [ActionType] is a way to provide more information about a [SemanticAction]. It can be used by
* other frameworks to identify an action that is to be run.
*
* Right now we are just using this interface as a common base class.
* */
interface ActionType
// These are some example action types provided by the framework:
@Suppress("Unused")
class Autofill : ActionType
enum class PolarityAction : ActionType { Positive, Negative }
enum class AccessibilityAction : ActionType { Primary, Secondary }
@Suppress("Unused")
enum class EditAction : ActionType { Cut, Copy, Paste, Select, SelectAll, Clear, Undo }
@Suppress("Unused")
enum class NavigationAction : ActionType { Back, Forward, Up, Down, Left, Right }
/**
* A PressGestureDetector that uses actions instead of lambda callbacks.
*
* This component just wraps the GestureDetetor and allows us to use it with actions, instead of
* lambda functions. A [SemanticAction] allows us to specify more information in addition to the
* lambda to be executed.*/
@Suppress("Unused")
@Composable
fun PressGestureDetectorWithActions(
onPress: SemanticAction<PxPosition> = SemanticAction(defaultParam = PxPosition.Origin) { },
onRelease: SemanticAction<Unit> = SemanticAction(defaultParam = Unit) { },
onCancel: SemanticAction<Unit> = SemanticAction(defaultParam = Unit) { },
children: @Composable() () -> Unit
) {
PressGestureDetector(
onPress = { onPress.action(ActionParam(ActionCaller.PointerInput, it)) },
onRelease = { onRelease.action(ActionParam(ActionCaller.PointerInput, Unit)) },
onCancel = { onCancel.action(ActionParam(ActionCaller.PointerInput, Unit)) },
children = children)
}
/**
* This is our lowest level API for Semantics.
*
* This implementation is a component just wraps its child components in a frame that has buttons
* which represent some frameworks that will trigger the semantic actions.
*/
@Suppress("Unused")
@Composable
fun Semantics(
@Suppress("UNUSED_PARAMETER") properties: Set<SemanticProperty<out Any>> = setOf(),
actions: Set<SemanticAction<out Any?>> = setOf(),
children: @Composable() () -> Unit
) {
Column(LayoutExpandedHeight) {
MaterialTheme {
Collapsable {
InvokeActionsByType(actions)
InvokeActionsByPhrase(actions)
InvokeActionsByAssistantAction(actions)
InvokeActionsByParameters(actions)
}
}
Row(LayoutExpandedWidth, arrangement = Arrangement.Center) {
Container(
height = 300.dp,
width = 500.dp,
modifier = LayoutGravity.Center,
children = children
)
}
}
}
/**
* This component adds buttons to invoke actions based on the accessibility action type.
*/
@Composable
private fun InvokeActionsByType(actions: Set<SemanticAction<out Any?>> = setOf()) {
val primary = actions.firstOrNull { it.types.contains(AccessibilityAction.Primary) }
val secondary =
actions.firstOrNull { it.types.contains(AccessibilityAction.Secondary) }
Text(text = "Accessibility Actions By Type", style = (+MaterialTheme.typography()).h6)
Row(LayoutExpandedWidth, arrangement = Arrangement.SpaceEvenly) {
Button(
text = "Primary",
onClick = { primary?.invoke(ActionCaller.Accessibility) })
Button(text = "Secondary", onClick = {
secondary?.invoke(ActionCaller.Accessibility)
})
}
}
/**
* This component adds buttons to invoke actions based on the accessibility action phrase.
*/
@Composable
private fun InvokeActionsByPhrase(actions: Set<SemanticAction<out Any?>> = setOf()) {
Text(
text = "Accessibility Actions By Phrase",
style = (+MaterialTheme.typography()).h6)
Row(LayoutExpandedWidth, arrangement = Arrangement.SpaceEvenly) {
actions.forEach {
Button(
text = it.phrase,
onClick = { it.invoke(ActionCaller.Accessibility) })
}
}
}
/**
* This component adds buttons to invoke actions using the assistant.
*/
@Composable
private fun InvokeActionsByAssistantAction(actions: Set<SemanticAction<out Any?>> = setOf()) {
val positive = actions.firstOrNull { it.types.contains(PolarityAction.Positive) }
val negative = actions.firstOrNull { it.types.contains(PolarityAction.Negative) }
Text(text = "Assistant Actions", style = (+MaterialTheme.typography()).h6)
Row(LayoutExpandedWidth, arrangement = Arrangement.SpaceEvenly) {
Button(
text = "Negative",
onClick = { negative?.invoke(ActionCaller.Assistant) })
Button(
text = "Positive",
onClick = { positive?.invoke(ActionCaller.Assistant) })
}
}
/**
* This component adds buttons to invoke actions based on the parameter type.
* It is a more realistic example where the framework using the action will first find out the type
* of action before invoking it.
*/
@Composable
private fun InvokeActionsByParameters(actions: Set<SemanticAction<out Any?>> = setOf()) {
@Suppress("UNCHECKED_CAST")
val pxPositionAction =
actions.firstOrNull { it.defaultParam is PxPosition } as SemanticAction<PxPosition>?
@Suppress("UNCHECKED_CAST")
val unitAction =
actions.firstOrNull { it.defaultParam is Unit } as SemanticAction<Unit>?
Text(text = "Actions using Parameters", style = (+MaterialTheme.typography()).h6)
Row(LayoutExpandedWidth, arrangement = Arrangement.SpaceEvenly) {
Button(
text = "IntAction",
onClick = { pxPositionAction?.invoke(param = PxPosition(1.px, 1.px)) })
Button(
text = "VoidAction",
onClick = { unitAction?.invoke(param = Unit) })
}
}
/**
* Enum class used by the [Collapsable] component.
*/
private enum class CollapseMode { Visible, Collapsed }
/**
* This composable wraps its children with a container and adds a show/hide button, to hide the
* children or make them visible.
*/
@Composable
private fun Collapsable(children: @Composable() () -> Unit) {
val collapsedState = +state { CollapseMode.Collapsed }
Row(LayoutExpandedWidth, arrangement = Arrangement.SpaceEvenly) {
Button(text = "Show/Hide Actions", onClick = {
collapsedState.value = when (collapsedState.value) {
CollapseMode.Collapsed -> CollapseMode.Visible
CollapseMode.Visible -> CollapseMode.Collapsed
}
})
}
if (collapsedState.value == CollapseMode.Visible) {
children()
}
}