Skip to content

Commit e4fefcf

Browse files
committed
Disable, rather than remove items from the (old) desktop text context menus
1 parent 394c57d commit e4fefcf

File tree

5 files changed

+270
-111
lines changed

5 files changed

+270
-111
lines changed

compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/swingexample/Main.jvm.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,15 +265,17 @@ fun CustomTextMenuProvider(content: @Composable () -> Unit) {
265265

266266
private fun AnnotatedString.crop() = if (length <= 5) toString() else "${take(5)}..."
267267

268+
@OptIn(ExperimentalFoundationApi::class)
268269
private fun swingItem(
269270
label: String,
270271
color: java.awt.Color,
271272
key: Int,
272-
onClick: () -> Unit
273+
menuItemAction: TextContextMenu.Action
273274
) = JMenuItem(label).apply {
274275
icon = circleIcon(color)
275276
accelerator = KeyStroke.getKeyStroke(key, if (hostOs.isMacOS) META_DOWN_MASK else CTRL_DOWN_MASK)
276-
addActionListener { onClick() }
277+
isEnabled = menuItemAction.enabled()
278+
addActionListener { menuItemAction.action() }
277279
}
278280

279281
private fun circleIcon(color: java.awt.Color) = object : Icon {

compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/BasicContextMenuRepresentation.desktop.kt

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import androidx.compose.foundation.text.contextmenu.data.TextContextMenuItemWith
2121
import androidx.compose.foundation.text.contextmenu.data.TextContextMenuSession
2222
import androidx.compose.runtime.Composable
2323
import androidx.compose.runtime.DisposableEffect
24+
import androidx.compose.runtime.SideEffect
2425
import androidx.compose.runtime.derivedStateOf
2526
import androidx.compose.runtime.getValue
2627
import androidx.compose.runtime.remember
@@ -65,42 +66,48 @@ val DarkDefaultContextMenuRepresentation = DefaultContextMenuRepresentation(
6566
class DefaultContextMenuRepresentation(
6667
private val backgroundColor: Color,
6768
private val textColor: Color,
68-
private val itemHoverColor: Color
69+
private val itemHoverColor: Color,
70+
private val disabledTextColor: Color = textColor.copy(alpha = 0.38f),
6971
) : ContextMenuRepresentation {
7072
@OptIn(ExperimentalComposeUiApi::class)
7173
@Composable
7274
override fun Representation(state: ContextMenuState, items: () -> List<ContextMenuItem>) {
7375
val status = state.status
74-
if (status is ContextMenuState.Status.Open) {
75-
val session = remember(state) {
76-
object : TextContextMenuSession {
77-
override fun close() {
78-
state.status = ContextMenuState.Status.Closed
79-
}
76+
if (status !is ContextMenuState.Status.Open) return
77+
78+
val session = remember(state) {
79+
object : TextContextMenuSession {
80+
override fun close() {
81+
state.status = ContextMenuState.Status.Closed
8082
}
8183
}
82-
val components by remember {
83-
derivedStateOf {
84-
items().map {
85-
TextContextMenuItemWithComposableLeadingIcon(
86-
key = it,
87-
label = it.label,
88-
enabled = true,
89-
onClick = {
90-
session.close()
91-
it.onClick()
92-
}
93-
)
94-
}
84+
}
85+
val components by remember {
86+
derivedStateOf {
87+
items().map {
88+
TextContextMenuItemWithComposableLeadingIcon(
89+
key = it,
90+
label = it.label,
91+
enabled = (it as? ContextMenuItemWithEnabledState)?.enabled() != false,
92+
onClick = {
93+
session.close()
94+
it.onClick()
95+
}
96+
)
9597
}
9698
}
97-
val colors = remember(backgroundColor, textColor, itemHoverColor) {
99+
}
100+
101+
if (components.isEmpty()) {
102+
SideEffect { session.close() }
103+
} else {
104+
val colors = remember(backgroundColor, textColor, itemHoverColor, disabledTextColor) {
98105
ContextMenuColors(
99106
backgroundColor = backgroundColor,
100107
textColor = textColor,
101-
iconColor = Color.Unspecified,
102-
disabledTextColor = Color.Unspecified,
103-
disabledIconColor = Color.Unspecified,
108+
iconColor = textColor,
109+
disabledTextColor = disabledTextColor,
110+
disabledIconColor = disabledTextColor,
104111
hoverColor = itemHoverColor,
105112
)
106113
}

compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ContextMenuProvider.desktop.kt

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,7 @@ open class ContextMenuItem(
194194
) {
195195
override fun equals(other: Any?): Boolean {
196196
if (this === other) return true
197-
if (other == null || this::class != other::class) return false
198-
199-
other as ContextMenuItem
197+
if (other !is ContextMenuItem) return false
200198

201199
if (label != other.label) return false
202200
if (onClick != other.onClick) return false
@@ -215,6 +213,41 @@ open class ContextMenuItem(
215213
}
216214
}
217215

216+
/**
217+
* Individual element of context menu.
218+
*
219+
* @param label The text to be displayed as a context menu item.
220+
* @param enabled Whether the menu item should be enabled.
221+
* @param onClick The action to be executed after click on the item.
222+
*/
223+
@ExperimentalFoundationApi
224+
open class ContextMenuItemWithEnabledState(
225+
label: String,
226+
val enabled: () -> Boolean,
227+
onClick: () -> Unit
228+
) : ContextMenuItem(label, onClick) {
229+
230+
override fun equals(other: Any?): Boolean {
231+
if (this === other) return true
232+
if (other !is ContextMenuItemWithEnabledState) return false
233+
234+
if (!super.equals(other)) return false
235+
if (enabled != other.enabled) return false
236+
237+
return true
238+
}
239+
240+
override fun hashCode(): Int {
241+
var result = super.hashCode()
242+
result = 31 * result + enabled.hashCode()
243+
return result
244+
}
245+
246+
override fun toString(): String {
247+
return "ContextMenuItemWithEnabledState(label='$label', enabled=$enabled)"
248+
}
249+
}
250+
218251
/**
219252
* Data container contains all [ContextMenuItem]s were defined previously in the hierarchy.
220253
* [ContextMenuRepresentation] uses it to display context menu.

0 commit comments

Comments
 (0)