/*
* Copyright 2022 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.material3
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.interaction.Interaction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.rememberScrollState
import androidx.compose.material3.internal.DropdownMenuPositionProvider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
/**
* Material Design dropdown menu.
*
* Menus display a list of choices on a temporary surface. They appear when users interact with a
* button, action, or other control.
*
* 
*
* A [DropdownMenu] behaves similarly to a [Popup], and will use the position of the parent layout
* to position itself on screen. Commonly a [DropdownMenu] will be placed in a [Box] with a sibling
* that will be used as the 'anchor'. Note that a [DropdownMenu] by itself will not take up any
* space in a layout, as the menu is displayed in a separate window, on top of other content.
*
* The [content] of a [DropdownMenu] will typically be [DropdownMenuItem]s, as well as custom
* content. Using [DropdownMenuItem]s will result in a menu that matches the Material
* specification for menus. Also note that the [content] is placed inside a scrollable [Column],
* so using a [LazyColumn] as the root layout inside [content] is unsupported.
*
* [onDismissRequest] will be called when the menu should close - for example when there is a
* tap outside the menu, or when the back key is pressed.
*
* [DropdownMenu] changes its positioning depending on the available space, always trying to be
* fully visible. Depending on layout direction, first it will try to align its start to the start
* of its parent, then its end to the end of its parent, and then to the edge of the window.
* Vertically, it will try to align its top to the bottom of its parent, then its bottom to top of
* its parent, and then to the edge of the window.
*
* An [offset] can be provided to adjust the positioning of the menu for cases when the layout
* bounds of its parent do not coincide with its visual bounds.
*
* Example usage:
* @sample androidx.compose.material3.samples.MenuSample
*
* Example usage with a [ScrollState] to control the menu items scroll position:
* @sample androidx.compose.material3.samples.MenuWithScrollStateSample
*
* @param expanded whether the menu is expanded or not
* @param onDismissRequest called when the user requests to dismiss the menu, such as by tapping
* outside the menu's bounds
* @param modifier [Modifier] to be applied to the menu's content
* @param offset [DpOffset] from the original position of the menu. The offset respects the
* [LayoutDirection], so the offset's x position will be added in LTR and subtracted in RTL.
* @param scrollState a [ScrollState] to used by the menu's content for items vertical scrolling
* @param properties [PopupProperties] for further customization of this popup's behavior
* @param shape the shape of the menu
* @param containerColor the container color of the menu
* @param tonalElevation when [containerColor] is [ColorScheme.surface], a translucent primary color
* overlay is applied on top of the container. A higher tonal elevation value will result in a
* darker color in light theme and lighter color in dark theme. See also: [Surface].
* @param shadowElevation the elevation for the shadow below the menu
* @param border the border to draw around the container of the menu. Pass `null` for no border.
* @param content the content of this dropdown menu, typically a [DropdownMenuItem]
*/
@Composable
fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true),
shape: Shape = MenuDefaults.shape,
containerColor: Color = MenuDefaults.containerColor,
tonalElevation: Dp = MenuDefaults.TonalElevation,
shadowElevation: Dp = MenuDefaults.ShadowElevation,
border: BorderStroke? = null,
content: @Composable ColumnScope.() -> Unit
) {
val expandedState = remember { MutableTransitionState(false) }
expandedState.targetState = expanded
if (expandedState.currentState || expandedState.targetState) {
val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) }
val density = LocalDensity.current
val popupPositionProvider = remember(offset, density) {
DropdownMenuPositionProvider(
offset,
density
) { parentBounds, menuBounds ->
transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds)
}
}
Popup(
onDismissRequest = onDismissRequest,
popupPositionProvider = popupPositionProvider,
properties = properties
) {
DropdownMenuContent(
expandedState = expandedState,
transformOriginState = transformOriginState,
scrollState = scrollState,
shape = shape,
containerColor = containerColor,
tonalElevation = tonalElevation,
shadowElevation = shadowElevation,
border = border,
modifier = modifier,
content = content,
)
}
}
}
@Deprecated(
level = DeprecationLevel.HIDDEN,
replaceWith = ReplaceWith(
expression = "DropdownMenu(\n" +
" expanded = expanded,\n" +
" onDismissRequest = onDismissRequest,\n" +
" modifier = modifier,\n" +
" offset = offset,\n" +
" scrollState = scrollState,\n" +
" properties = properties,\n" +
" shape = MenuDefaults.shape,\n" +
" containerColor = MenuDefaults.containerColor,\n" +
" tonalElevation = MenuDefaults.TonalElevation,\n" +
" shadowElevation = MenuDefaults.ShadowElevation,\n" +
" border = null,\n" +
" content = content,\n" +
")",
),
message = "Maintained for binary compatibility. Use overload with parameters for shape, " +
"color, elevation, and border."
)
@Composable
fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
scrollState: ScrollState = rememberScrollState(),
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit
) = DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
modifier = modifier,
offset = offset,
scrollState = scrollState,
properties = properties,
shape = MenuDefaults.shape,
containerColor = MenuDefaults.containerColor,
tonalElevation = MenuDefaults.TonalElevation,
shadowElevation = MenuDefaults.ShadowElevation,
border = null,
content = content,
)
@Deprecated(
level = DeprecationLevel.HIDDEN,
replaceWith = ReplaceWith(
expression = "DropdownMenu(expanded,onDismissRequest, modifier, offset, " +
"rememberScrollState(), properties, content)",
"androidx.compose.foundation.rememberScrollState"
),
message = "Replaced by a DropdownMenu function with a ScrollState parameter"
)
@Composable
fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier = Modifier,
offset: DpOffset = DpOffset(0.dp, 0.dp),
properties: PopupProperties = PopupProperties(focusable = true),
content: @Composable ColumnScope.() -> Unit
) = DropdownMenu(
expanded = expanded,
onDismissRequest = onDismissRequest,
modifier = modifier,
offset = offset,
scrollState = rememberScrollState(),
properties = properties,
content = content
)
/**
* Material Design dropdown menu item.
*
* Menus display a list of choices on a temporary surface. They appear when users interact with a
* button, action, or other control.
*
* 
*
* Example usage:
* @sample androidx.compose.material3.samples.MenuSample
*
* @param text text of the menu item
* @param onClick called when this menu item is clicked
* @param modifier the [Modifier] to be applied to this menu item
* @param leadingIcon optional leading icon to be displayed at the beginning of the item's text
* @param trailingIcon optional trailing icon to be displayed at the end of the item's text. This
* trailing icon slot can also accept [Text] to indicate a keyboard shortcut.
* @param enabled controls the enabled state of this menu item. When `false`, this component will
* not respond to user input, and it will appear visually disabled and disabled to accessibility
* services.
* @param colors [MenuItemColors] that will be used to resolve the colors used for this menu item in
* different states. See [MenuDefaults.itemColors].
* @param contentPadding the padding applied to the content of this menu item
* @param interactionSource an optional hoisted [MutableInteractionSource] for observing and
* emitting [Interaction]s for this menu item. You can use this to change the menu item's appearance
* or preview the menu item in different states. Note that if `null` is provided, interactions will
* still happen internally.
*/
@Composable
fun DropdownMenuItem(
text: @Composable () -> Unit,
onClick: () -> Unit,
modifier: Modifier = Modifier,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
enabled: Boolean = true,
colors: MenuItemColors = MenuDefaults.itemColors(),
contentPadding: PaddingValues = MenuDefaults.DropdownMenuItemContentPadding,
interactionSource: MutableInteractionSource? = null,
) {
DropdownMenuItemContent(
text = text,
onClick = onClick,
modifier = modifier,
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
enabled = enabled,
colors = colors,
contentPadding = contentPadding,
interactionSource = interactionSource,
)
}