
skikoMain.androidx.compose.material.Menu.skiko.kt Maven / Gradle / Ivy
/*
* Copyright 2023 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.material
import androidx.compose.animation.core.MutableTransitionState
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.layout.RowScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.input.InputMode
import androidx.compose.ui.input.InputModeManager
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalInputModeManager
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
import kotlin.jvm.JvmName
/**
* Material Design dropdown menu.
*
* A dropdown menu is a compact way of displaying multiple choices. It appears upon interaction with
* an element (such as an icon or button) or when users perform a specific action.
*
* 
*
* 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. It will try to expand horizontally, depending on layout direction, to the end of
* its parent, then to the start of its parent, and then screen end-aligned. Vertically, it will
* try to expand to the bottom of its parent, then from the top of its parent, and then screen
* top-aligned. 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. Note the offset will
* be applied in the direction in which the menu will decide to expand.
*
* Example usage:
* @sample androidx.compose.material.samples.MenuSample
*
* Example usage with a [ScrollState] to control the menu items scroll position:
* @sample androidx.compose.material.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] to be added to the position of the menu
* @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 content the content of this dropdown menu, typically a [DropdownMenuItem]
*/
@Composable
actual fun DropdownMenu(
expanded: Boolean,
onDismissRequest: () -> Unit,
modifier: Modifier,
offset: DpOffset,
scrollState: ScrollState,
properties: PopupProperties,
content: @Composable ColumnScope.() -> Unit
) {
val expandedStates = remember { MutableTransitionState(false) }
expandedStates.targetState = expanded
if (expandedStates.currentState || expandedStates.targetState) {
val transformOriginState = remember { mutableStateOf(TransformOrigin.Center) }
val density = LocalDensity.current
val popupPositionProvider = DropdownMenuPositionProvider(
offset,
density
) { parentBounds, menuBounds ->
transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds)
}
var focusManager: FocusManager? by mutableStateOf(null)
var inputModeManager: InputModeManager? by mutableStateOf(null)
Popup(
onDismissRequest = onDismissRequest,
popupPositionProvider = popupPositionProvider,
properties = properties,
onKeyEvent = {
handlePopupOnKeyEvent(it, focusManager, inputModeManager)
},
) {
focusManager = LocalFocusManager.current
inputModeManager = LocalInputModeManager.current
DropdownMenuContent(
expandedStates = expandedStates,
transformOriginState = transformOriginState,
scrollState = scrollState,
modifier = modifier,
content = content
)
}
}
}
@OptIn(ExperimentalComposeUiApi::class)
internal fun handlePopupOnKeyEvent(
keyEvent: KeyEvent,
focusManager: FocusManager?,
inputModeManager: InputModeManager?
): Boolean = if (keyEvent.type == KeyEventType.KeyDown) {
when (keyEvent.key) {
Key.DirectionDown -> {
inputModeManager?.requestInputMode(InputMode.Keyboard)
focusManager?.moveFocus(FocusDirection.Next)
true
}
Key.DirectionUp -> {
inputModeManager?.requestInputMode(InputMode.Keyboard)
focusManager?.moveFocus(FocusDirection.Previous)
true
}
else -> false
}
} else {
false
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy