-
Notifications
You must be signed in to change notification settings - Fork 5.7k
[JEWEL-1205] Fix Menu Items Looking Too Tall in Presentation Mode #3419
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ import androidx.compose.runtime.CompositionLocalProvider | |
| import androidx.compose.runtime.DisposableEffect | ||
| import androidx.compose.runtime.Immutable | ||
| import androidx.compose.runtime.LaunchedEffect | ||
| import androidx.compose.runtime.MutableState | ||
| import androidx.compose.runtime.derivedStateOf | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.runtime.mutableStateOf | ||
|
|
@@ -67,7 +68,6 @@ import org.jetbrains.annotations.ApiStatus | |
| import org.jetbrains.jewel.foundation.GenerateDataFunctions | ||
| import org.jetbrains.jewel.foundation.InternalJewelApi | ||
| import org.jetbrains.jewel.foundation.modifier.onHover | ||
| import org.jetbrains.jewel.foundation.modifier.thenIf | ||
| import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Active | ||
| import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Enabled | ||
| import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Focused | ||
|
|
@@ -87,9 +87,7 @@ import org.jetbrains.jewel.ui.component.styling.LocalMenuStyle | |
| import org.jetbrains.jewel.ui.component.styling.MenuItemColors | ||
| import org.jetbrains.jewel.ui.component.styling.MenuItemMetrics | ||
| import org.jetbrains.jewel.ui.component.styling.MenuStyle | ||
| import org.jetbrains.jewel.ui.disabledAppearance | ||
| import org.jetbrains.jewel.ui.icon.IconKey | ||
| import org.jetbrains.jewel.ui.painter.hints.Stateful | ||
| import org.jetbrains.jewel.ui.popupShadowAndBorder | ||
| import org.jetbrains.jewel.ui.theme.menuStyle | ||
| import org.jetbrains.skiko.hostOs | ||
|
|
@@ -821,36 +819,18 @@ internal fun MenuItemBase( | |
| style: MenuStyle = JewelTheme.menuStyle, | ||
| @Suppress("DEPRECATION") content: @Composable (itemState: MenuItemState) -> Unit, | ||
| ) { | ||
| var itemState by | ||
| remember(interactionSource) { | ||
| @Suppress("DEPRECATION") mutableStateOf(MenuItemState.of(selected = selected, enabled = enabled)) | ||
| } | ||
|
|
||
| remember(enabled, selected) { itemState = itemState.copy(selected = selected, enabled = enabled) } | ||
| val itemState by rememberMenuItemState(selected, enabled, interactionSource) | ||
|
|
||
| val focusRequester = remember { FocusRequester() } | ||
| val menuController = LocalMenuController.current | ||
| val localInputModeManager = LocalInputModeManager.current | ||
|
|
||
| LaunchedEffect(interactionSource) { | ||
| interactionSource.interactions.collect { interaction -> | ||
| when (interaction) { | ||
| is PressInteraction.Press -> itemState = itemState.copy(pressed = true) | ||
| is PressInteraction.Cancel, | ||
| is PressInteraction.Release -> itemState = itemState.copy(pressed = false) | ||
| is HoverInteraction.Enter -> { | ||
| itemState = itemState.copy(hovered = true) | ||
| focusRequester.requestFocus() | ||
| } | ||
|
|
||
| is HoverInteraction.Exit -> itemState = itemState.copy(hovered = false) | ||
| is FocusInteraction.Focus -> itemState = itemState.copy(focused = true) | ||
| is FocusInteraction.Unfocus -> itemState = itemState.copy(focused = false) | ||
| } | ||
| LaunchedEffect(itemState.isHovered) { | ||
| if (itemState.isHovered) { | ||
| focusRequester.requestFocus() | ||
| } | ||
| } | ||
|
|
||
| val menuController = LocalMenuController.current | ||
| val localInputModeManager = LocalInputModeManager.current | ||
|
|
||
| Box( | ||
| modifier = | ||
| modifier | ||
|
|
@@ -872,52 +852,24 @@ internal fun MenuItemBase( | |
| onDispose {} | ||
| } | ||
|
|
||
| val itemColors = style.colors.itemColors | ||
| val itemMetrics = style.metrics.itemMetrics | ||
|
|
||
| @Suppress("DEPRECATION") // Not really deprecated, will be made internal | ||
| val updatedTextStyle = LocalTextStyle.current.copy(color = itemColors.contentFor(itemState).value) | ||
|
|
||
| @Suppress("DEPRECATION") // Not really deprecated, will be made internal | ||
| CompositionLocalProvider( | ||
| LocalContentColor provides itemColors.contentFor(itemState).value, | ||
| LocalTextStyle provides updatedTextStyle, | ||
| ) { | ||
| val backgroundColor by itemColors.backgroundFor(itemState) | ||
|
|
||
| Row( | ||
| modifier = | ||
| Modifier.fillMaxWidth() | ||
| .defaultMinSize(minHeight = itemMetrics.minHeight) | ||
| .drawItemBackground(itemMetrics, backgroundColor) | ||
| .padding(itemMetrics.contentPadding), | ||
| horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
| verticalAlignment = Alignment.CenterVertically, | ||
| ) { | ||
| if (canShowIcon) { | ||
| val iconModifier = Modifier.size(style.metrics.itemMetrics.iconSize) | ||
| if (iconKey != null) { | ||
| Icon( | ||
| key = iconKey, | ||
| contentDescription = null, | ||
| modifier = iconModifier.thenIf(!enabled) { disabledAppearance() }, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we've lost the disabledAppearance here, unless I'm missing something |
||
| MenuItemLayout( | ||
| itemState = itemState, | ||
| style = style, | ||
| iconKey = iconKey, | ||
| canShowIcon = canShowIcon, | ||
| trailingContent = | ||
| if (canShowKeybinding) { | ||
| { | ||
| @Suppress("DEPRECATION") // Not really deprecated, MenuItemColors will be made internal | ||
| Text( | ||
| modifier = Modifier.padding(style.metrics.itemMetrics.keybindingsPadding), | ||
| text = keybindingHint, | ||
| color = style.colors.itemColors.keybindingTintFor(itemState).value, | ||
| ) | ||
| } else { | ||
| Box(modifier = iconModifier) | ||
| } | ||
| } | ||
|
|
||
| Box(modifier = Modifier.weight(1f, true)) { content(itemState) } | ||
|
|
||
| if (canShowKeybinding) { | ||
| Text( | ||
| modifier = Modifier.padding(style.metrics.itemMetrics.keybindingsPadding), | ||
| text = keybindingHint, | ||
| color = itemColors.keybindingTintFor(itemState).value, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| } else null, | ||
| content = { content(itemState) }, | ||
| ) | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -951,43 +903,15 @@ internal fun MenuSubmenuItem( | |
| style: MenuStyle = JewelTheme.menuStyle, | ||
| @Suppress("DEPRECATION") content: @Composable (itemState: MenuItemState) -> Unit, | ||
| ) { | ||
| var itemState by | ||
| remember(interactionSource) { | ||
| @Suppress("DEPRECATION") mutableStateOf(MenuItemState.of(selected = selected, enabled = enabled)) | ||
| } | ||
|
|
||
| remember(enabled) { itemState = itemState.copy(selected = false, enabled = enabled) } | ||
|
|
||
| var itemState by rememberMenuItemState(selected, enabled, interactionSource) | ||
| val focusRequester = remember { FocusRequester() } | ||
|
|
||
| LaunchedEffect(interactionSource) { | ||
| interactionSource.interactions.collect { interaction -> | ||
| when (interaction) { | ||
| is PressInteraction.Press -> itemState = itemState.copy(pressed = true) | ||
| is PressInteraction.Cancel, | ||
| is PressInteraction.Release -> itemState = itemState.copy(pressed = false) | ||
|
|
||
| is HoverInteraction.Enter -> itemState = itemState.copy(hovered = true) | ||
| is HoverInteraction.Exit -> itemState = itemState.copy(hovered = false) | ||
| is FocusInteraction.Focus -> itemState = itemState.copy(focused = true) | ||
| is FocusInteraction.Unfocus -> itemState = itemState.copy(focused = false) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| remember(selected) { itemState = itemState.copy(selected = selected) } | ||
| LaunchedEffect(itemState.isSelected) { if (itemState.isSelected) focusRequester.requestFocus() } | ||
|
|
||
| val itemColors = style.colors.itemColors | ||
| val menuMetrics = style.metrics | ||
|
|
||
| @Suppress("DEPRECATION") // Not really deprecated, will be made internal | ||
| val backgroundColor by itemColors.backgroundFor(itemState) | ||
| Box( | ||
| modifier = | ||
| modifier | ||
| .fillMaxWidth() | ||
| .drawItemBackground(menuMetrics.itemMetrics, backgroundColor) | ||
| .focusRequester(focusRequester) | ||
| .clickable( | ||
| onClick = { itemState = itemState.copy(selected = !itemState.isSelected) }, | ||
|
|
@@ -1004,32 +928,22 @@ internal fun MenuSubmenuItem( | |
| } | ||
| } | ||
| ) { | ||
| @Suppress("DEPRECATION") // Not really deprecated, will be made internal | ||
| CompositionLocalProvider(LocalContentColor provides itemColors.contentFor(itemState).value) { | ||
| Row( | ||
| Modifier.fillMaxWidth().padding(menuMetrics.itemMetrics.contentPadding), | ||
| verticalAlignment = Alignment.CenterVertically, | ||
| horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
| ) { | ||
| if (showIcon) { | ||
| if (iconKey != null) { | ||
| Icon(key = iconKey, contentDescription = null) | ||
| } else { | ||
| Box(Modifier.size(style.metrics.itemMetrics.iconSize)) | ||
| } | ||
| } | ||
|
|
||
| Box(Modifier.weight(1f)) { content(itemState) } | ||
|
|
||
| MenuItemLayout( | ||
| itemState = itemState, | ||
| style = style, | ||
| iconKey = iconKey, | ||
| canShowIcon = showIcon, | ||
| trailingContent = { | ||
| @Suppress("DEPRECATION") // Not really deprecated, MenuItemColors will be made internal | ||
| Icon( | ||
| key = style.icons.submenuChevron, | ||
| tint = itemColors.iconTintFor(itemState).value, | ||
| contentDescription = null, | ||
| modifier = Modifier.size(style.metrics.itemMetrics.iconSize), | ||
| hint = Stateful(itemState), | ||
| tint = style.colors.itemColors.iconTintFor(itemState).value, | ||
| ) | ||
| } | ||
| } | ||
| }, | ||
| content = { content(itemState) }, | ||
| ) | ||
|
|
||
| if (itemState.isSelected) { | ||
| Submenu( | ||
|
|
@@ -1048,6 +962,60 @@ internal fun MenuSubmenuItem( | |
| } | ||
| } | ||
|
|
||
| @Suppress("DEPRECATION") // Not really deprecated, MenuItemColors be made internal | ||
| @Composable | ||
| internal fun MenuItemLayout( | ||
| itemState: MenuItemState, | ||
| modifier: Modifier = Modifier, | ||
| style: MenuStyle = JewelTheme.menuStyle, | ||
| iconKey: IconKey? = null, | ||
| canShowIcon: Boolean = true, | ||
| trailingContent: (@Composable () -> Unit)? = null, | ||
| content: @Composable () -> Unit, | ||
| ) { | ||
| val itemColors = style.colors.itemColors | ||
| val itemMetrics = style.metrics.itemMetrics | ||
|
|
||
| val contentColor = itemColors.contentFor(itemState).value | ||
| val backgroundColor by itemColors.backgroundFor(itemState) | ||
|
|
||
| CompositionLocalProvider( | ||
| LocalContentColor provides contentColor, | ||
| LocalTextStyle provides LocalTextStyle.current.copy(color = contentColor), | ||
| ) { | ||
| Row( | ||
| modifier = | ||
| modifier | ||
| .fillMaxWidth() | ||
| .defaultMinSize(minHeight = itemMetrics.minHeight) | ||
| .drawItemBackground(itemMetrics, backgroundColor) | ||
| .padding(itemMetrics.contentPadding), | ||
| horizontalArrangement = Arrangement.spacedBy(4.dp), | ||
| verticalAlignment = Alignment.CenterVertically, | ||
| ) { | ||
| if (canShowIcon) { | ||
| val iconModifier = Modifier.size(itemMetrics.iconSize) | ||
| if (iconKey != null) { | ||
| Icon( | ||
| key = iconKey, | ||
| contentDescription = null, | ||
| modifier = iconModifier, | ||
| tint = itemColors.iconTintFor(itemState).value, | ||
| ) | ||
| } else { | ||
| Box(modifier = iconModifier) | ||
| } | ||
| } | ||
|
|
||
| Box(modifier = Modifier.weight(1f)) { content() } | ||
|
|
||
| if (trailingContent != null) { | ||
| trailingContent() | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun Modifier.drawItemBackground(itemMetrics: MenuItemMetrics, backgroundColor: Color) = drawBehind { | ||
| val cornerSizePx = itemMetrics.selectionCornerSize.toPx(size, density = this) | ||
| val cornerRadius = CornerRadius(cornerSizePx, cornerSizePx) | ||
|
|
@@ -1201,3 +1169,32 @@ public value class MenuItemState(public val state: ULong) : SelectableComponentS | |
| } | ||
| } | ||
| } | ||
|
|
||
| @Suppress("DEPRECATION") // Not really deprecated, MenuItemState will be made internal | ||
| @Composable | ||
| internal fun rememberMenuItemState( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't this subtly change |
||
| selected: Boolean, | ||
| enabled: Boolean, | ||
| interactionSource: MutableInteractionSource, | ||
| ): MutableState<MenuItemState> { | ||
| val itemState = | ||
| remember(interactionSource) { mutableStateOf(MenuItemState.of(selected = selected, enabled = enabled)) } | ||
|
|
||
| remember(enabled, selected) { itemState.value = itemState.value.copy(selected = selected, enabled = enabled) } | ||
|
|
||
| LaunchedEffect(interactionSource) { | ||
| interactionSource.interactions.collect { interaction -> | ||
| when (interaction) { | ||
| is PressInteraction.Press -> itemState.value = itemState.value.copy(pressed = true) | ||
| is PressInteraction.Cancel, | ||
| is PressInteraction.Release -> itemState.value = itemState.value.copy(pressed = false) | ||
| is HoverInteraction.Enter -> itemState.value = itemState.value.copy(hovered = true) | ||
| is HoverInteraction.Exit -> itemState.value = itemState.value.copy(hovered = false) | ||
| is FocusInteraction.Focus -> itemState.value = itemState.value.copy(focused = true) | ||
| is FocusInteraction.Unfocus -> itemState.value = itemState.value.copy(focused = false) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return itemState | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just curious, is it true that there might be more places where we need to use it but we don't know about them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that I was able to find yet 😮
We do fetch the
rowHeight()value in ourreadFromLaF()function over inBridgeGlobalMetricsbut we are already unscaling it thereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm we do unscaling for
Insetsin this file *seeInsets.toPaddingValues()), so if theInts we get are indeed scaled, we need to check. I don't know if it's a potential issue, but it seems likeretrieveIntAsDp*()s do need a closer look to validate whether they're fetching the right values or not.Otherwise you're saying "it's fine to use
retrieveIntAsDp*s everywhere except in this one case where it would do a double scale", which seems a bit odd in principle. Effectively,JBUI.CurrentTheme.List.rowHeight()does the same thing we do, callingJBUI.getInt()which callsUIManager.get()—retrieveIntAsDp*()also does the same, then calling.dpon the value (which is where the double scaling may happen).