Skip to content

Commit 827595d

Browse files
[JEWEL-1184] Add DefaultSplitButton Without Jewel's Dropdown
1 parent 2136e53 commit 827595d

File tree

4 files changed

+137
-2
lines changed

4 files changed

+137
-2
lines changed

platform/jewel/samples/showcase/src/main/kotlin/org/jetbrains/jewel/samples/showcase/components/Buttons.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
package org.jetbrains.jewel.samples.showcase.components
44

5+
import androidx.compose.foundation.background
6+
import androidx.compose.foundation.border
57
import androidx.compose.foundation.layout.Arrangement
68
import androidx.compose.foundation.layout.Box
79
import androidx.compose.foundation.layout.Column
@@ -13,6 +15,7 @@ import androidx.compose.foundation.layout.height
1315
import androidx.compose.foundation.layout.padding
1416
import androidx.compose.foundation.layout.size
1517
import androidx.compose.foundation.layout.width
18+
import androidx.compose.foundation.shape.RoundedCornerShape
1619
import androidx.compose.runtime.Composable
1720
import androidx.compose.runtime.getValue
1821
import androidx.compose.runtime.mutableIntStateOf
@@ -22,8 +25,16 @@ import androidx.compose.runtime.setValue
2225
import androidx.compose.ui.Alignment
2326
import androidx.compose.ui.Modifier
2427
import androidx.compose.ui.graphics.Color
28+
import androidx.compose.ui.layout.onGloballyPositioned
29+
import androidx.compose.ui.layout.positionInParent
30+
import androidx.compose.ui.platform.LocalDensity
2531
import androidx.compose.ui.text.style.TextOverflow
32+
import androidx.compose.ui.unit.IntOffset
33+
import androidx.compose.ui.unit.IntSize
2634
import androidx.compose.ui.unit.dp
35+
import androidx.compose.ui.unit.round
36+
import androidx.compose.ui.window.Popup
37+
import androidx.compose.ui.window.PopupProperties
2738
import org.jetbrains.jewel.foundation.theme.JewelTheme
2839
import org.jetbrains.jewel.foundation.util.JewelLogger
2940
import org.jetbrains.jewel.ui.component.ActionButton
@@ -52,6 +63,7 @@ import org.jetbrains.jewel.ui.painter.badge.DotBadgeShape
5263
import org.jetbrains.jewel.ui.painter.hints.Badge
5364
import org.jetbrains.jewel.ui.painter.hints.Selected
5465
import org.jetbrains.jewel.ui.painter.hints.Stroke
66+
import org.jetbrains.jewel.ui.theme.defaultSplitButtonStyle
5567
import org.jetbrains.jewel.ui.theme.outlinedSplitButtonStyle
5668
import org.jetbrains.jewel.ui.theme.transparentIconButtonStyle
5769

@@ -380,6 +392,44 @@ private fun SplitButtons() {
380392
},
381393
)
382394

395+
var showComposePopup by remember { mutableStateOf(false) }
396+
var buttonSize by remember { mutableStateOf(IntSize.Zero) }
397+
var buttonPosition by remember { mutableStateOf(IntOffset.Zero) }
398+
val buttonExpandedOutline =
399+
with(LocalDensity.current) {
400+
JewelTheme.defaultSplitButtonStyle.button.metrics.focusOutlineExpand.roundToPx()
401+
}
402+
Box {
403+
DefaultSplitButton(
404+
modifier =
405+
Modifier.onGloballyPositioned { coordinates ->
406+
buttonSize = coordinates.size
407+
buttonPosition = coordinates.positionInParent().round()
408+
},
409+
onClick = {},
410+
secondaryOnClick = { showComposePopup = true },
411+
content = { SingleLineText("Split button w/ Compose Popup") },
412+
)
413+
if (showComposePopup) {
414+
Popup(
415+
offset =
416+
IntOffset(buttonPosition.x, buttonPosition.y + buttonSize.height + buttonExpandedOutline),
417+
onDismissRequest = { showComposePopup = false },
418+
properties = PopupProperties(),
419+
) {
420+
Box(
421+
modifier =
422+
Modifier.background(JewelTheme.globalColors.panelBackground, RoundedCornerShape(8.dp))
423+
.border(3.dp, JewelTheme.globalColors.outlines.warning, RoundedCornerShape(8.dp))
424+
.padding(16.dp),
425+
contentAlignment = Alignment.Center,
426+
) {
427+
Text("Compose Popup")
428+
}
429+
}
430+
}
431+
}
432+
383433
Tooltip(
384434
tooltip = {
385435
Text("This button is intentionally too narrow, to check that it works when space-constrained.")

platform/jewel/ui/api-dump.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ f:org.jetbrains.jewel.ui.component.BannerKt
132132
- sf:WarningDefaultBanner(java.lang.String,androidx.compose.ui.Modifier,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function3,org.jetbrains.jewel.ui.component.styling.DefaultBannerStyle,androidx.compose.ui.text.TextStyle,androidx.compose.runtime.Composer,I,I):V
133133
f:org.jetbrains.jewel.ui.component.ButtonKt
134134
- sf:DefaultButton(kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,Z,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.ButtonStyle,androidx.compose.ui.text.TextStyle,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V
135+
- sf:DefaultSplitButton(kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,Z,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,androidx.compose.ui.text.TextStyle,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V
135136
- bsf:DefaultSplitButton(kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,Z,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,androidx.compose.ui.text.TextStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function1,androidx.compose.runtime.Composer,I,I):V
136137
- bsf:DefaultSplitButton(kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function0,androidx.compose.ui.Modifier,Z,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,androidx.compose.ui.text.TextStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,kotlin.jvm.functions.Function2,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I):V
137138
- sf:DefaultSplitButton-zTVvCW4(kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function1,androidx.compose.ui.Modifier,androidx.compose.ui.Modifier,F,F,Z,androidx.compose.foundation.interaction.MutableInteractionSource,org.jetbrains.jewel.ui.component.styling.SplitButtonStyle,androidx.compose.ui.text.TextStyle,org.jetbrains.jewel.ui.component.styling.MenuStyle,kotlin.jvm.functions.Function0,kotlin.jvm.functions.Function2,androidx.compose.runtime.Composer,I,I,I):V

platform/jewel/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Button.kt

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,61 @@ public fun DefaultSplitButton(
479479
)
480480
}
481481

482+
/**
483+
* A split button combining a primary action WITHOUT a dropdown menu, using the default visual style.
484+
*
485+
* Provides two interactive areas: the main button area for the primary action and a chevron section that will only
486+
* invoke the `secondaryClick` callback.
487+
*
488+
* **IMPORTANT:** This overload does NOT manage any popup/dropdown. You are responsible for handling the lifecycle,
489+
* positioning, and disposal of any UI you wish to show when `secondaryOnClick` is invoked. Use this when you need
490+
* custom popup behavior (e.g., JBPopupFactory in Bridge or Compose Popup) instead of relying on Jewel's own Popup.
491+
*
492+
* **Guidelines:** [on IJP SDK webhelp](https://plugins.jetbrains.com/docs/intellij/split-button.html)
493+
*
494+
* **Usage example:**
495+
* [`Buttons.kt`](https://github.com/JetBrains/intellij-community/blob/master/platform/jewel/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt)
496+
*
497+
* **Swing equivalent:**
498+
* [`JBOptionButton`](https://github.com/JetBrains/intellij-community/tree/idea/243.22562.145/platform/platform-api/src/com/intellij/ui/components/JBOptionButton.kt)
499+
*
500+
* @param onClick Will be called when the user clicks the main button area
501+
* @param modifier Modifier to be applied to the button
502+
* @param enabled Controls the enabled state of the button. When false, the button will not be clickable
503+
* @param interactionSource An optional [MutableInteractionSource] for observing and emitting [Interaction]s for this
504+
* button
505+
* @param style The visual styling configuration for the split button including colors, metrics and layout parameters
506+
* @param textStyle The typography style to be applied to the button's text content
507+
* @param menuStyle The visual styling configuration for the dropdown menu
508+
* @param secondaryOnClick Will be called when the user clicks the dropdown/chevron section
509+
* @param content The content to be displayed in the main button area
510+
* @see com.intellij.ui.components.JBOptionButton
511+
*/
512+
@Composable
513+
public fun DefaultSplitButton(
514+
onClick: () -> Unit,
515+
modifier: Modifier = Modifier,
516+
enabled: Boolean = true,
517+
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
518+
style: SplitButtonStyle = JewelTheme.defaultSplitButtonStyle,
519+
textStyle: TextStyle = JewelTheme.defaultTextStyle,
520+
secondaryOnClick: () -> Unit = {},
521+
content: @Composable () -> Unit,
522+
) {
523+
SplitButtonImpl(
524+
onClick = onClick,
525+
secondaryOnClick = secondaryOnClick,
526+
enabled = enabled,
527+
interactionSource = interactionSource,
528+
style = style,
529+
textStyle = textStyle,
530+
menuStyle = null,
531+
isDefault = true,
532+
modifier = modifier,
533+
content = content,
534+
)
535+
}
536+
482537
/**
483538
* A split button combining a primary action with a dropdown menu, using the default visual style.
484539
*
@@ -672,7 +727,7 @@ private fun SplitButtonImpl(
672727
interactionSource: MutableInteractionSource,
673728
style: SplitButtonStyle,
674729
textStyle: TextStyle,
675-
menuStyle: MenuStyle,
730+
menuStyle: MenuStyle?,
676731
isDefault: Boolean,
677732
modifier: Modifier = Modifier,
678733
popupModifier: Modifier = Modifier,
@@ -737,7 +792,7 @@ private fun SplitButtonImpl(
737792
},
738793
)
739794

740-
if (popupVisible && enabled) {
795+
if (popupVisible && enabled && menuStyle != null) {
741796
val splitButtonPopupModifier =
742797
Modifier.heightIn(max = maxPopupHeight)
743798
.widthIn(min = buttonWidth, max = maxPopupWidth.coerceAtLeast(buttonWidth))

plugins/devkit/intellij.devkit.compose/src/demo/SwingComparisonTabPanel.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import androidx.compose.foundation.layout.size
1313
import androidx.compose.foundation.layout.width
1414
import androidx.compose.foundation.text.input.rememberTextFieldState
1515
import androidx.compose.runtime.Composable
16+
import androidx.compose.runtime.LaunchedEffect
1617
import androidx.compose.runtime.getValue
1718
import androidx.compose.runtime.mutableIntStateOf
19+
import androidx.compose.runtime.mutableStateOf
1820
import androidx.compose.runtime.remember
1921
import androidx.compose.runtime.setValue
2022
import androidx.compose.ui.Modifier
@@ -30,8 +32,10 @@ import com.intellij.icons.AllIcons
3032
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI
3133
import com.intellij.openapi.editor.colors.EditorFontType
3234
import com.intellij.openapi.ui.ComboBox
35+
import com.intellij.openapi.ui.popup.JBPopupFactory
3336
import com.intellij.openapi.util.IconLoader
3437
import com.intellij.ui.JBColor
38+
import com.intellij.ui.awt.RelativePoint
3539
import com.intellij.ui.components.BrowserLink
3640
import com.intellij.ui.components.JBLabel
3741
import com.intellij.ui.components.JBScrollPane
@@ -48,6 +52,7 @@ import org.jetbrains.jewel.bridge.JewelComposePanel
4852
import org.jetbrains.jewel.bridge.retrieveEditorColorScheme
4953
import org.jetbrains.jewel.foundation.theme.JewelTheme
5054
import org.jetbrains.jewel.ui.component.DefaultButton
55+
import org.jetbrains.jewel.ui.component.DefaultSplitButton
5156
import org.jetbrains.jewel.ui.component.EditableListComboBox
5257
import org.jetbrains.jewel.ui.component.ExternalLink
5358
import org.jetbrains.jewel.ui.component.Icon
@@ -60,6 +65,7 @@ import org.jetbrains.jewel.ui.disabledAppearance
6065
import org.jetbrains.jewel.ui.icons.AllIconsKeys
6166
import org.jetbrains.jewel.ui.theme.textAreaStyle
6267
import org.jetbrains.jewel.ui.typography
68+
import java.awt.MouseInfo
6369
import javax.swing.BoxLayout
6470
import javax.swing.DefaultComboBoxModel
6571
import javax.swing.JLabel
@@ -146,6 +152,29 @@ internal class SwingComparisonTabPanel : BorderLayoutPanel() {
146152
.align(AlignY.CENTER)
147153
.applyToComponent { putClientProperty(DarculaButtonUI.DEFAULT_STYLE_KEY, true) }
148154
compose { DefaultButton({}) { Text("Default Compose Button") } }
155+
156+
compose {
157+
var showPopup by remember { mutableStateOf(false) }
158+
159+
DefaultSplitButton(
160+
onClick = {},
161+
secondaryOnClick = {
162+
showPopup = !showPopup
163+
},
164+
) {
165+
Text("Default Compose Button")
166+
}
167+
168+
LaunchedEffect(showPopup) {
169+
if (showPopup) {
170+
val mouseLocation = MouseInfo.getPointerInfo().location
171+
JBPopupFactory.getInstance()
172+
.createMessage("Hi! This is an example")
173+
.show(RelativePoint.fromScreen(mouseLocation))
174+
showPopup = false
175+
}
176+
}
177+
}
149178
}
150179
.layout(RowLayout.PARENT_GRID)
151180
}

0 commit comments

Comments
 (0)