diff --git a/foundation/api/foundation.api b/foundation/api/foundation.api index 823feb411..bca54ac66 100644 --- a/foundation/api/foundation.api +++ b/foundation/api/foundation.api @@ -759,15 +759,20 @@ public abstract interface class org/jetbrains/jewel/foundation/state/SelectableC public abstract fun isSelected ()Z } -public abstract interface class org/jetbrains/jewel/foundation/state/ToggleableComponentState : org/jetbrains/jewel/foundation/state/SelectableComponentState { +public abstract interface class org/jetbrains/jewel/foundation/state/ToggleableComponentState { public static final field Companion Lorg/jetbrains/jewel/foundation/state/ToggleableComponentState$Companion; public abstract fun getToggleableState ()Landroidx/compose/ui/state/ToggleableState; + public abstract fun isSelected ()Z } public final class org/jetbrains/jewel/foundation/state/ToggleableComponentState$Companion { public final fun readToggleableState-VKZWuLQ (J)Landroidx/compose/ui/state/ToggleableState; } +public final class org/jetbrains/jewel/foundation/state/ToggleableComponentState$DefaultImpls { + public static fun isSelected (Lorg/jetbrains/jewel/foundation/state/ToggleableComponentState;)Z +} + public abstract interface class org/jetbrains/jewel/foundation/theme/JewelTheme { public static final field Companion Lorg/jetbrains/jewel/foundation/theme/JewelTheme$Companion; } diff --git a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/state/ToggleableComponentState.kt b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/state/ToggleableComponentState.kt index 183e877ef..43d97d258 100644 --- a/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/state/ToggleableComponentState.kt +++ b/foundation/src/main/kotlin/org/jetbrains/jewel/foundation/state/ToggleableComponentState.kt @@ -4,9 +4,12 @@ import androidx.compose.ui.state.ToggleableState import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Indeterminate import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Selected -public interface ToggleableComponentState : SelectableComponentState { +public interface ToggleableComponentState { public val toggleableState: ToggleableState + public val isSelected: Boolean + get() = toggleableState == ToggleableState.On + public companion object { public fun ULong.readToggleableState(): ToggleableState { val selected = this and Selected != 0UL diff --git a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt index 41f8055d2..03a9489f6 100644 --- a/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt +++ b/samples/ide-plugin/src/main/kotlin/org/jetbrains/jewel/samples/ideplugin/ComponentShowcaseTab.kt @@ -51,6 +51,7 @@ import org.jetbrains.jewel.ui.component.DefaultButton import org.jetbrains.jewel.ui.component.Divider import org.jetbrains.jewel.ui.component.Dropdown import org.jetbrains.jewel.ui.component.Icon +import org.jetbrains.jewel.ui.component.IconActionButton import org.jetbrains.jewel.ui.component.IconButton import org.jetbrains.jewel.ui.component.LazyTree import org.jetbrains.jewel.ui.component.OutlinedButton @@ -285,9 +286,14 @@ private fun IconsShowcase() { PlatformIcon(AllIconsKeys.Actions.Close, "Close") } - IconButton(onClick = { }, Modifier.size(24.dp)) { - PlatformIcon(AllIconsKeys.Actions.AddList, "Close") - } + IconActionButton( + AllIconsKeys.Actions.AddList, + "Close", + onClick = { }, + modifier = Modifier.size(24.dp), + hints = arrayOf(Size(24)), + tooltip = { Text("Hello there") }, + ) } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/ComponentsView.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/ComponentsView.kt index 4218c41da..62c73968f 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/ComponentsView.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/ComponentsView.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -24,16 +23,12 @@ import org.jetbrains.jewel.samples.standalone.viewmodel.View import org.jetbrains.jewel.samples.standalone.viewmodel.ViewInfo import org.jetbrains.jewel.ui.Orientation import org.jetbrains.jewel.ui.component.Divider -import org.jetbrains.jewel.ui.component.Icon -import org.jetbrains.jewel.ui.component.SelectableIconButton +import org.jetbrains.jewel.ui.component.SelectableIconActionButton import org.jetbrains.jewel.ui.component.Text -import org.jetbrains.jewel.ui.component.Tooltip import org.jetbrains.jewel.ui.component.Typography -import org.jetbrains.jewel.ui.component.styling.LocalIconButtonStyle import org.jetbrains.jewel.ui.component.styling.TooltipMetrics import org.jetbrains.jewel.ui.component.styling.TooltipStyle import org.jetbrains.jewel.ui.painter.hints.Size -import org.jetbrains.jewel.ui.painter.hints.Stroke import org.jetbrains.jewel.ui.theme.tooltipStyle import kotlin.time.Duration.Companion.milliseconds @@ -51,24 +46,21 @@ fun ComponentsView() { fun ComponentsToolBar() { Column(Modifier.fillMaxHeight().width(40.dp).verticalScroll(rememberScrollState())) { ComponentsViewModel.views.forEach { - Tooltip( + SelectableIconActionButton( + key = it.iconKey, + contentDescription = "Show ${it.title}", + selected = ComponentsViewModel.currentView == it, + onClick = { ComponentsViewModel.currentView = it }, + modifier = Modifier.size(40.dp).padding(5.dp), tooltip = { Text("Show ${it.title}") }, - style = + tooltipStyle = TooltipStyle( JewelTheme.tooltipStyle.colors, TooltipMetrics.defaults(showDelay = 150.milliseconds), ), tooltipPlacement = TooltipPlacement.ComponentRect(Alignment.CenterEnd, Alignment.CenterEnd), - ) { - SelectableIconButton( - selected = ComponentsViewModel.currentView == it, - onClick = { ComponentsViewModel.currentView = it }, - modifier = Modifier.size(40.dp).padding(5.dp), - ) { state -> - val tint by LocalIconButtonStyle.current.colors.foregroundFor(state) - Icon(it.iconKey, null, hints = arrayOf(Size(20), Stroke(tint))) - } - } + extraHints = arrayOf(Size(20)), + ) } } } diff --git a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt index 09d6d310c..00f8e1acb 100644 --- a/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt +++ b/samples/standalone/src/main/kotlin/org/jetbrains/jewel/samples/standalone/view/component/Buttons.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -19,8 +20,11 @@ import org.jetbrains.jewel.ui.component.IconActionButton import org.jetbrains.jewel.ui.component.IconButton import org.jetbrains.jewel.ui.component.OutlinedButton import org.jetbrains.jewel.ui.component.PlatformIcon +import org.jetbrains.jewel.ui.component.SelectableIconActionButton import org.jetbrains.jewel.ui.component.SelectableIconButton import org.jetbrains.jewel.ui.component.Text +import org.jetbrains.jewel.ui.component.ToggleableIconActionButton +import org.jetbrains.jewel.ui.component.ToggleableIconButton import org.jetbrains.jewel.ui.component.Typography import org.jetbrains.jewel.ui.component.styling.LocalIconButtonStyle import org.jetbrains.jewel.ui.icons.AllIconsKeys @@ -34,8 +38,11 @@ fun Buttons() { verticalArrangement = Arrangement.spacedBy(16.dp), ) { NormalButtons() - IconButtons() - IconActionButtons() + + var selectedIndex by remember { mutableIntStateOf(0) } + IconButtons(selectedIndex == 1) { selectedIndex = 1 } + IconActionButtons(selectedIndex == 2) { selectedIndex = 2 } + ActionButtons() } } @@ -66,7 +73,10 @@ private fun NormalButtons() { } @Composable -private fun IconButtons() { +private fun IconButtons( + selected: Boolean, + onSelectableClick: () -> Unit, +) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -88,20 +98,34 @@ private fun IconButtons() { Text("Selectable:") - var selected by remember { mutableStateOf(false) } - SelectableIconButton(onClick = { selected = !selected }, selected = selected) { state -> - val tint by LocalIconButtonStyle.current.colors.foregroundFor(state) + SelectableIconButton(onClick = onSelectableClick, selected = selected) { state -> + val tint by LocalIconButtonStyle.current.colors.selectableForegroundFor(state) PlatformIcon( key = AllIconsKeys.Actions.MatchCase, - contentDescription = "IconButton", + contentDescription = "SelectableIconButton", hints = arrayOf(Selected(selected), Stroke(tint)), ) } + + Text("Toggleable:") + + var checked by remember { mutableStateOf(false) } + ToggleableIconButton(onValueChange = { checked = !checked }, value = checked) { state -> + val tint by LocalIconButtonStyle.current.colors.toggleableForegroundFor(state) + PlatformIcon( + key = AllIconsKeys.Actions.MatchCase, + contentDescription = "ToggleableIconButton", + hints = arrayOf(Selected(checked), Stroke(tint)), + ) + } } } @Composable -private fun IconActionButtons() { +private fun IconActionButtons( + selected: Boolean, + onSelectableClick: () -> Unit, +) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp), @@ -110,14 +134,29 @@ private fun IconActionButtons() { Text("IconActionButton", style = Typography.h4TextStyle()) Text("With tooltip:") - - IconActionButton(key = AllIconsKeys.Actions.Copy, contentDescription = "IconButton", onClick = {}) { + IconActionButton(key = AllIconsKeys.Actions.Copy, contentDescription = "IconActionButton", onClick = {}) { Text("I am a tooltip") } Text("Without tooltip:") + IconActionButton(key = AllIconsKeys.Actions.Copy, contentDescription = "IconActionButton", onClick = {}) - IconActionButton(key = AllIconsKeys.Actions.Copy, contentDescription = "IconButton", onClick = {}) + Text("Selectable:") + SelectableIconActionButton( + key = AllIconsKeys.Actions.Copy, + contentDescription = "SelectableIconActionButton", + selected = selected, + onClick = onSelectableClick, + ) + + Text("Toggleable:") + var checked by remember { mutableStateOf(false) } + ToggleableIconActionButton( + key = AllIconsKeys.Actions.Copy, + contentDescription = "SelectableIconActionButton", + value = checked, + onValueChange = { checked = it }, + ) } } diff --git a/ui/api/ui.api b/ui/api/ui.api index 33584e925..7d51441a4 100644 --- a/ui/api/ui.api +++ b/ui/api/ui.api @@ -308,24 +308,29 @@ public final class org/jetbrains/jewel/ui/component/GroupHeaderKt { public final class org/jetbrains/jewel/ui/component/IconActionButtonKt { public static final fun IconActionButton (Landroidx/compose/ui/graphics/painter/Painter;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Landroidx/compose/runtime/Composer;II)V - public static final fun IconActionButton (Landroidx/compose/ui/graphics/painter/Painter;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Landroidx/compose/foundation/TooltipPlacement;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V - public static final fun IconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Landroidx/compose/runtime/Composer;II)V - public static final fun IconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Landroidx/compose/foundation/TooltipPlacement;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V + public static final fun IconActionButton (Landroidx/compose/ui/graphics/painter/Painter;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/TooltipPlacement;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V + public static final fun IconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;[Lorg/jetbrains/jewel/ui/painter/PainterHint;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Landroidx/compose/runtime/Composer;II)V + public static final fun IconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;[Lorg/jetbrains/jewel/ui/painter/PainterHint;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/TooltipPlacement;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V + public static final fun SelectableIconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;[Lorg/jetbrains/jewel/ui/painter/PainterHint;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Landroidx/compose/runtime/Composer;III)V + public static final fun SelectableIconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;[Lorg/jetbrains/jewel/ui/painter/PainterHint;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/TooltipPlacement;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V + public static final fun ToggleableIconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;[Lorg/jetbrains/jewel/ui/painter/PainterHint;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Landroidx/compose/runtime/Composer;III)V + public static final fun ToggleableIconActionButton (Lorg/jetbrains/jewel/ui/icon/IconKey;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;[Lorg/jetbrains/jewel/ui/painter/PainterHint;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Landroidx/compose/ui/Modifier;Landroidx/compose/foundation/TooltipPlacement;Landroidx/compose/foundation/interaction/MutableInteractionSource;Ljava/lang/Class;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;III)V } public final class org/jetbrains/jewel/ui/component/IconButtonKt { public static final fun IconButton (Lkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V public static final fun SelectableIconButton (ZLkotlin/jvm/functions/Function0;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V + public static final fun ToggleableIconButton (ZLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZZLorg/jetbrains/jewel/ui/component/styling/IconButtonStyle;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V } -public final class org/jetbrains/jewel/ui/component/IconButtonState : org/jetbrains/jewel/foundation/state/FocusableComponentState, org/jetbrains/jewel/foundation/state/SelectableComponentState { +public final class org/jetbrains/jewel/ui/component/IconButtonState : org/jetbrains/jewel/foundation/state/FocusableComponentState { public static final field Companion Lorg/jetbrains/jewel/ui/component/IconButtonState$Companion; public static final synthetic fun box-impl (J)Lorg/jetbrains/jewel/ui/component/IconButtonState; public fun chooseValue (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public static fun chooseValue-impl (JLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; public static fun constructor-impl (J)J - public static final fun copy-iT7zR20 (JZZZZZZ)J - public static synthetic fun copy-iT7zR20$default (JZZZZZZILjava/lang/Object;)J + public static final fun copy-3OtLUoY (JZZZZZ)J + public static synthetic fun copy-3OtLUoY$default (JZZZZZILjava/lang/Object;)J public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (JLjava/lang/Object;)Z public static final fun equals-impl0 (JJ)Z @@ -342,16 +347,14 @@ public final class org/jetbrains/jewel/ui/component/IconButtonState : org/jetbra public static fun isHovered-impl (J)Z public fun isPressed ()Z public static fun isPressed-impl (J)Z - public fun isSelected ()Z - public static fun isSelected-impl (J)Z public fun toString ()Ljava/lang/String; public static fun toString-impl (J)Ljava/lang/String; public final synthetic fun unbox-impl ()J } public final class org/jetbrains/jewel/ui/component/IconButtonState$Companion { - public final fun of-iT7zR20 (ZZZZZZ)J - public static synthetic fun of-iT7zR20$default (Lorg/jetbrains/jewel/ui/component/IconButtonState$Companion;ZZZZZZILjava/lang/Object;)J + public final fun of-3OtLUoY (ZZZZZ)J + public static synthetic fun of-3OtLUoY$default (Lorg/jetbrains/jewel/ui/component/IconButtonState$Companion;ZZZZZILjava/lang/Object;)J } public final class org/jetbrains/jewel/ui/component/IconKt { @@ -664,6 +667,42 @@ public final class org/jetbrains/jewel/ui/component/SegmentedControlState$Compan public static synthetic fun of-bhrFvog$default (Lorg/jetbrains/jewel/ui/component/SegmentedControlState$Companion;ZZZZZILjava/lang/Object;)J } +public final class org/jetbrains/jewel/ui/component/SelectableIconButtonState : org/jetbrains/jewel/foundation/state/FocusableComponentState, org/jetbrains/jewel/foundation/state/SelectableComponentState { + public static final field Companion Lorg/jetbrains/jewel/ui/component/SelectableIconButtonState$Companion; + public static final synthetic fun box-impl (J)Lorg/jetbrains/jewel/ui/component/SelectableIconButtonState; + public fun chooseValue (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public static fun chooseValue-impl (JLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public static fun constructor-impl (J)J + public static final fun copy-z9lnavA (JZZZZZZ)J + public static synthetic fun copy-z9lnavA$default (JZZZZZZILjava/lang/Object;)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public final fun getState-s-VKNKU ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public fun isActive ()Z + public static fun isActive-impl (J)Z + public fun isEnabled ()Z + public static fun isEnabled-impl (J)Z + public fun isFocused ()Z + public static fun isFocused-impl (J)Z + public fun isHovered ()Z + public static fun isHovered-impl (J)Z + public fun isPressed ()Z + public static fun isPressed-impl (J)Z + public fun isSelected ()Z + public static fun isSelected-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class org/jetbrains/jewel/ui/component/SelectableIconButtonState$Companion { + public final fun of-z9lnavA (ZZZZZZ)J + public static synthetic fun of-z9lnavA$default (Lorg/jetbrains/jewel/ui/component/SelectableIconButtonState$Companion;ZZZZZZILjava/lang/Object;)J +} + public final class org/jetbrains/jewel/ui/component/SliderKt { public static final fun Slider (FLkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZLkotlin/ranges/ClosedFloatingPointRange;ILkotlin/jvm/functions/Function0;Landroidx/compose/foundation/interaction/MutableInteractionSource;Lorg/jetbrains/jewel/ui/component/styling/SliderStyle;Landroidx/compose/runtime/Composer;II)V } @@ -843,6 +882,44 @@ public final class org/jetbrains/jewel/ui/component/TextKt { public static final fun Text-bAzTDeA (Ljava/lang/String;Landroidx/compose/ui/Modifier;JJLandroidx/compose/ui/text/font/FontStyle;Landroidx/compose/ui/text/font/FontWeight;Landroidx/compose/ui/text/font/FontFamily;JLandroidx/compose/ui/text/style/TextDecoration;IJIZILkotlin/jvm/functions/Function1;Landroidx/compose/ui/text/TextStyle;Landroidx/compose/runtime/Composer;III)V } +public final class org/jetbrains/jewel/ui/component/ToggleableIconButtonState : org/jetbrains/jewel/foundation/state/FocusableComponentState, org/jetbrains/jewel/foundation/state/ToggleableComponentState { + public static final field Companion Lorg/jetbrains/jewel/ui/component/ToggleableIconButtonState$Companion; + public static final synthetic fun box-impl (J)Lorg/jetbrains/jewel/ui/component/ToggleableIconButtonState; + public fun chooseValue (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public static fun chooseValue-impl (JLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Landroidx/compose/runtime/Composer;I)Ljava/lang/Object; + public static fun constructor-impl (J)J + public static final fun copy-CAf7mdk (JZLandroidx/compose/ui/state/ToggleableState;ZZZZ)J + public static synthetic fun copy-CAf7mdk$default (JZLandroidx/compose/ui/state/ToggleableState;ZZZZILjava/lang/Object;)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public final fun getState-s-VKNKU ()J + public fun getToggleableState ()Landroidx/compose/ui/state/ToggleableState; + public static fun getToggleableState-impl (J)Landroidx/compose/ui/state/ToggleableState; + public fun hashCode ()I + public static fun hashCode-impl (J)I + public fun isActive ()Z + public static fun isActive-impl (J)Z + public fun isEnabled ()Z + public static fun isEnabled-impl (J)Z + public fun isFocused ()Z + public static fun isFocused-impl (J)Z + public fun isHovered ()Z + public static fun isHovered-impl (J)Z + public fun isPressed ()Z + public static fun isPressed-impl (J)Z + public fun isSelected ()Z + public static fun isSelected-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class org/jetbrains/jewel/ui/component/ToggleableIconButtonState$Companion { + public final fun of-CAf7mdk (ZLandroidx/compose/ui/state/ToggleableState;ZZZZ)J + public static synthetic fun of-CAf7mdk$default (Lorg/jetbrains/jewel/ui/component/ToggleableIconButtonState$Companion;ZLandroidx/compose/ui/state/ToggleableState;ZZZZILjava/lang/Object;)J +} + public final class org/jetbrains/jewel/ui/component/TooltipKt { public static final fun Tooltip (Lkotlin/jvm/functions/Function2;Landroidx/compose/ui/Modifier;Lorg/jetbrains/jewel/ui/component/styling/TooltipStyle;Landroidx/compose/foundation/TooltipPlacement;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)V public static final fun rememberPopupPositionProviderAtFixedPosition-7KAyTs4 (JJLandroidx/compose/ui/Alignment;FLandroidx/compose/runtime/Composer;II)Landroidx/compose/ui/window/PopupPositionProvider; @@ -1323,7 +1400,6 @@ public final class org/jetbrains/jewel/ui/component/styling/IconButtonColors { public final fun backgroundFor-e04Q3fE (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; public final fun borderFor-e04Q3fE (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; public fun equals (Ljava/lang/Object;)Z - public final fun foregroundFor-e04Q3fE (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; public final fun getBackground-0d7_KjU ()J public final fun getBackgroundDisabled-0d7_KjU ()J public final fun getBackgroundFocused-0d7_KjU ()J @@ -1340,7 +1416,13 @@ public final class org/jetbrains/jewel/ui/component/styling/IconButtonColors { public final fun getBorderSelectedActivated-0d7_KjU ()J public final fun getForegroundSelectedActivated-0d7_KjU ()J public fun hashCode ()I + public final fun selectableBackgroundFor-eXKw-ig (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; + public final fun selectableBorderFor-eXKw-ig (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; + public final fun selectableForegroundFor-eXKw-ig (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; public fun toString ()Ljava/lang/String; + public final fun toggleableBackgroundFor-hDcPvVY (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; + public final fun toggleableBorderFor-hDcPvVY (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; + public final fun toggleableForegroundFor-hDcPvVY (JLandroidx/compose/runtime/Composer;I)Landroidx/compose/runtime/State; } public final class org/jetbrains/jewel/ui/component/styling/IconButtonColors$Companion { diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt index e42bbf66a..c14680e13 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/Checkbox.kt @@ -319,7 +319,7 @@ private fun CheckboxImpl( } else { PainterHint.None }, - Selected(checkboxState), + Selected(checkboxState.toggleableState == ToggleableState.On), Stateful(checkboxState), ) @@ -378,9 +378,6 @@ public value class CheckboxState(private val state: ULong) : ToggleableComponent override val isActive: Boolean get() = state and Active != 0UL - override val isSelected: Boolean - get() = toggleableState != ToggleableState.Off - override val isFocused: Boolean get() = state and Focused != 0UL @@ -409,7 +406,7 @@ public value class CheckboxState(private val state: ULong) : ToggleableComponent override fun toString(): String = "${javaClass.simpleName}(toggleableState=$toggleableState, isEnabled=$isEnabled, isFocused=$isFocused, " + - "isHovered=$isHovered, isPressed=$isPressed, isSelected=$isSelected, isActive=$isActive)" + "isHovered=$isHovered, isPressed=$isPressed, isActive=$isActive)" public companion object { public fun of( diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconActionButton.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconActionButton.kt index 90c049ab4..e6f4cf52d 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconActionButton.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconActionButton.kt @@ -18,6 +18,7 @@ package org.jetbrains.jewel.ui.component import androidx.compose.foundation.TooltipPlacement import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter @@ -27,6 +28,8 @@ import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.styling.IconButtonStyle import org.jetbrains.jewel.ui.component.styling.TooltipStyle import org.jetbrains.jewel.ui.icon.IconKey +import org.jetbrains.jewel.ui.painter.PainterHint +import org.jetbrains.jewel.ui.painter.hints.Stroke import org.jetbrains.jewel.ui.theme.iconButtonStyle import org.jetbrains.jewel.ui.theme.tooltipStyle @@ -39,6 +42,7 @@ public fun IconActionButton( enabled: Boolean = true, focusable: Boolean = true, style: IconButtonStyle = JewelTheme.iconButtonStyle, + hints: Array = emptyArray(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, iconClass: Class<*> = key::class.java, ) { @@ -51,6 +55,7 @@ public fun IconActionButton( style, interactionSource, modifier, + hints = hints, onClick, ) } @@ -64,7 +69,9 @@ public fun IconActionButton( enabled: Boolean = true, focusable: Boolean = true, style: IconButtonStyle = JewelTheme.iconButtonStyle, + hints: Array = emptyArray(), tooltipStyle: TooltipStyle = JewelTheme.tooltipStyle, + tooltipModifier: Modifier = Modifier, tooltipPlacement: TooltipPlacement = FixedCursorPoint(offset = DpOffset(0.dp, 16.dp)), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, iconClass: Class<*> = key::class.java, @@ -73,17 +80,19 @@ public fun IconActionButton( Tooltip( tooltip, style = tooltipStyle, + modifier = tooltipModifier, tooltipPlacement = tooltipPlacement, - modifier = modifier, ) { CoreIconActionButton( key = key, + modifier = modifier, contentDescription = contentDescription, iconClass = iconClass, enabled = enabled, focusable = focusable, style = style, interactionSource = interactionSource, + hints = hints, onClick = onClick, ) } @@ -98,11 +107,190 @@ private fun CoreIconActionButton( focusable: Boolean, style: IconButtonStyle, interactionSource: MutableInteractionSource, - modifier: Modifier = Modifier, + modifier: Modifier, + hints: Array, onClick: () -> Unit, ) { IconButton(onClick, modifier, enabled, focusable, style, interactionSource) { - Icon(key, contentDescription, iconClass = iconClass) + Icon(key, contentDescription, iconClass = iconClass, hints = hints) + } +} + +@Composable +public fun SelectableIconActionButton( + key: IconKey, + contentDescription: String?, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + focusable: Boolean = true, + style: IconButtonStyle = JewelTheme.iconButtonStyle, + extraHints: Array = emptyArray(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + iconClass: Class<*> = key::class.java, +) { + CoreSelectableIconActionButton( + key, + contentDescription, + iconClass, + selected, + enabled, + focusable, + style, + interactionSource, + modifier, + extraHints, + onClick, + ) +} + +@Composable +public fun SelectableIconActionButton( + key: IconKey, + contentDescription: String?, + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + focusable: Boolean = true, + style: IconButtonStyle = JewelTheme.iconButtonStyle, + extraHints: Array = emptyArray(), + tooltipStyle: TooltipStyle = JewelTheme.tooltipStyle, + tooltipModifier: Modifier = Modifier, + tooltipPlacement: TooltipPlacement = FixedCursorPoint(offset = DpOffset(0.dp, 16.dp)), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + iconClass: Class<*> = key::class.java, + tooltip: @Composable () -> Unit, +) { + Tooltip( + tooltip, + style = tooltipStyle, + modifier = tooltipModifier, + tooltipPlacement = tooltipPlacement, + ) { + CoreSelectableIconActionButton( + key = key, + modifier = modifier, + contentDescription = contentDescription, + iconClass = iconClass, + selected = selected, + enabled = enabled, + focusable = focusable, + style = style, + interactionSource = interactionSource, + extraHints = extraHints, + onClick = onClick, + ) + } +} + +@Composable +private fun CoreSelectableIconActionButton( + key: IconKey, + contentDescription: String?, + iconClass: Class<*>, + selected: Boolean, + enabled: Boolean, + focusable: Boolean, + style: IconButtonStyle, + interactionSource: MutableInteractionSource, + modifier: Modifier, + extraHints: Array, + onClick: () -> Unit, +) { + SelectableIconButton(selected, onClick, modifier, enabled, focusable, style, interactionSource) { + val strokeColor by style.colors.selectableForegroundFor(it) + Icon(key, contentDescription, iconClass = iconClass, hints = arrayOf(Stroke(strokeColor), *extraHints)) + } +} + +@Composable +public fun ToggleableIconActionButton( + key: IconKey, + contentDescription: String?, + value: Boolean, + onValueChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + focusable: Boolean = true, + style: IconButtonStyle = JewelTheme.iconButtonStyle, + extraHints: Array = emptyArray(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + iconClass: Class<*> = key::class.java, +) { + CoreToggleableIconActionButton( + key, + contentDescription, + iconClass, + value, + enabled, + focusable, + style, + interactionSource, + modifier, + extraHints, + onValueChange, + ) +} + +@Composable +public fun ToggleableIconActionButton( + key: IconKey, + contentDescription: String?, + value: Boolean, + onValueChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + focusable: Boolean = true, + style: IconButtonStyle = JewelTheme.iconButtonStyle, + extraHints: Array = emptyArray(), + tooltipStyle: TooltipStyle = JewelTheme.tooltipStyle, + tooltipModifier: Modifier = Modifier, + tooltipPlacement: TooltipPlacement = FixedCursorPoint(offset = DpOffset(0.dp, 16.dp)), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + iconClass: Class<*> = key::class.java, + tooltip: @Composable () -> Unit, +) { + Tooltip( + tooltip, + style = tooltipStyle, + modifier = tooltipModifier, + tooltipPlacement = tooltipPlacement, + ) { + CoreToggleableIconActionButton( + key = key, + modifier = modifier, + contentDescription = contentDescription, + iconClass = iconClass, + value = value, + enabled = enabled, + focusable = focusable, + style = style, + interactionSource = interactionSource, + extraHints = extraHints, + onValueChange = onValueChange, + ) + } +} + +@Composable +private fun CoreToggleableIconActionButton( + key: IconKey, + contentDescription: String?, + iconClass: Class<*>, + value: Boolean, + enabled: Boolean, + focusable: Boolean, + style: IconButtonStyle, + interactionSource: MutableInteractionSource, + modifier: Modifier, + extraHints: Array, + onValueChange: (Boolean) -> Unit, +) { + ToggleableIconButton(value, onValueChange, modifier, enabled, focusable, style, interactionSource) { + val strokeColor by style.colors.toggleableForegroundFor(it) + Icon(key, contentDescription, iconClass = iconClass, hints = arrayOf(Stroke(strokeColor), *extraHints)) } } @@ -139,6 +327,7 @@ public fun IconActionButton( focusable: Boolean = true, style: IconButtonStyle = JewelTheme.iconButtonStyle, tooltipStyle: TooltipStyle = JewelTheme.tooltipStyle, + tooltipModifier: Modifier = Modifier, tooltipPlacement: TooltipPlacement = FixedCursorPoint(offset = DpOffset(0.dp, 16.dp)), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, tooltip: @Composable () -> Unit, @@ -146,11 +335,12 @@ public fun IconActionButton( Tooltip( tooltip, style = tooltipStyle, + modifier = tooltipModifier, tooltipPlacement = tooltipPlacement, - modifier = modifier, ) { CoreIconActionButton( painter = painter, + modifier = modifier, contentDescription = contentDescription, enabled = enabled, focusable = focusable, diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconButton.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconButton.kt index a8f07877e..59efc2a50 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconButton.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/IconButton.kt @@ -12,11 +12,11 @@ import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.selection.toggleable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -25,10 +25,19 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.focusProperties import androidx.compose.ui.semantics.Role +import androidx.compose.ui.state.ToggleableState import org.jetbrains.jewel.foundation.modifier.onActivated -import org.jetbrains.jewel.foundation.state.CommonStateBitMask +import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Active +import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Enabled +import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Focused +import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Hovered +import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Indeterminate +import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Pressed +import org.jetbrains.jewel.foundation.state.CommonStateBitMask.Selected import org.jetbrains.jewel.foundation.state.FocusableComponentState import org.jetbrains.jewel.foundation.state.SelectableComponentState +import org.jetbrains.jewel.foundation.state.ToggleableComponentState +import org.jetbrains.jewel.foundation.state.ToggleableComponentState.Companion.readToggleableState import org.jetbrains.jewel.foundation.theme.JewelTheme import org.jetbrains.jewel.ui.component.styling.IconButtonStyle import org.jetbrains.jewel.ui.theme.iconButtonStyle @@ -44,24 +53,50 @@ public fun IconButton( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable (BoxScope.(IconButtonState) -> Unit), ) { - val buttonState = remember(interactionSource) { mutableStateOf(IconButtonState.of(enabled = enabled)) } + var buttonState by remember(interactionSource) { + mutableStateOf(IconButtonState.of(enabled = enabled)) + } + + remember(enabled) { buttonState = buttonState.copy(enabled = enabled) } + + LaunchedEffect(key1 = interactionSource) { + interactionSource.interactions.collect { interaction -> + when (interaction) { + is PressInteraction.Press -> buttonState = buttonState.copy(pressed = true) + is PressInteraction.Cancel, + is PressInteraction.Release, + -> buttonState = buttonState.copy(pressed = false) + + is HoverInteraction.Enter -> buttonState = buttonState.copy(hovered = true) + is HoverInteraction.Exit -> buttonState = buttonState.copy(hovered = false) + is FocusInteraction.Focus -> buttonState = buttonState.copy(focused = focusable) + is FocusInteraction.Unfocus -> buttonState = buttonState.copy(focused = false) + } + } + } - remember(enabled) { buttonState.value = buttonState.value.copy(enabled = enabled) } + val shape = RoundedCornerShape(style.metrics.cornerSize) + val background by style.colors.backgroundFor(buttonState) + val border by style.colors.borderFor(buttonState) - IconButtonImpl( - state = buttonState, + Box( modifier = - modifier.clickable( - onClick = onClick, - enabled = enabled, - role = Role.Button, - interactionSource = interactionSource, - indication = null, - ), - style = style, - focusable = focusable, - interactionSource = interactionSource, - content = content, + Modifier + .thenIf(!focusable) { focusProperties { canFocus = false } } + .then(modifier) + .clickable( + onClick = onClick, + enabled = enabled, + role = Role.Button, + interactionSource = interactionSource, + indication = null, + ) + .defaultMinSize(style.metrics.minSize.width, style.metrics.minSize.height) + .padding(style.metrics.padding) + .background(background, shape) + .border(style.metrics.borderWidth, border, shape), + contentAlignment = Alignment.Center, + content = { content(buttonState) }, ) } @@ -74,21 +109,41 @@ public fun SelectableIconButton( focusable: Boolean = true, style: IconButtonStyle = JewelTheme.iconButtonStyle, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - content: @Composable (BoxScope.(IconButtonState) -> Unit), + content: @Composable (BoxScope.(SelectableIconButtonState) -> Unit), ) { - val buttonState = - remember(interactionSource) { - mutableStateOf(IconButtonState.of(enabled = enabled)) - } + var buttonState by remember(interactionSource) { + mutableStateOf(SelectableIconButtonState.of(enabled = enabled)) + } remember(enabled, selected) { - buttonState.value = buttonState.value.copy(enabled = enabled, selected = selected) + buttonState = buttonState.copy(enabled = enabled, selected = selected) + } + + LaunchedEffect(key1 = interactionSource) { + interactionSource.interactions.collect { interaction -> + when (interaction) { + is PressInteraction.Press -> buttonState = buttonState.copy(pressed = true) + is PressInteraction.Cancel, + is PressInteraction.Release, + -> buttonState = buttonState.copy(pressed = false) + + is HoverInteraction.Enter -> buttonState = buttonState.copy(hovered = true) + is HoverInteraction.Exit -> buttonState = buttonState.copy(hovered = false) + is FocusInteraction.Focus -> buttonState = buttonState.copy(focused = focusable) + is FocusInteraction.Unfocus -> buttonState = buttonState.copy(focused = false) + } + } } - IconButtonImpl( - state = buttonState, + val shape = RoundedCornerShape(style.metrics.cornerSize) + val background by style.colors.selectableBackgroundFor(buttonState) + val border by style.colors.selectableBorderFor(buttonState) + + Box( modifier = - modifier + Modifier + .thenIf(!focusable) { focusProperties { canFocus = false } } + .then(modifier) .selectable( onClick = onClick, enabled = enabled, @@ -98,27 +153,37 @@ public fun SelectableIconButton( selected = selected, ) .onActivated(enabled = enabled) { - buttonState.value = buttonState.value.copy(active = it) - }, - style = style, - focusable = focusable, - interactionSource = interactionSource, - content = content, + buttonState = buttonState.copy(active = it) + } + .defaultMinSize(style.metrics.minSize.width, style.metrics.minSize.height) + .padding(style.metrics.padding) + .background(background, shape) + .border(style.metrics.borderWidth, border, shape), + contentAlignment = Alignment.Center, + content = { content(buttonState) }, ) } @Composable -private fun IconButtonImpl( - state: MutableState, - modifier: Modifier, - style: IconButtonStyle, - focusable: Boolean, - interactionSource: MutableInteractionSource, - content: @Composable (BoxScope.(IconButtonState) -> Unit), +public fun ToggleableIconButton( + value: Boolean, + onValueChange: (Boolean) -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + focusable: Boolean = true, + style: IconButtonStyle = JewelTheme.iconButtonStyle, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable (BoxScope.(ToggleableIconButtonState) -> Unit), ) { - var buttonState by state + var buttonState by remember(interactionSource) { + mutableStateOf(ToggleableIconButtonState.of(enabled = enabled)) + } + + remember(enabled, value) { + buttonState = buttonState.copy(enabled = enabled, toggleableState = ToggleableState(value)) + } - LaunchedEffect(interactionSource) { + LaunchedEffect(key1 = interactionSource) { interactionSource.interactions.collect { interaction -> when (interaction) { is PressInteraction.Press -> buttonState = buttonState.copy(pressed = true) @@ -133,14 +198,27 @@ private fun IconButtonImpl( } } } + val shape = RoundedCornerShape(style.metrics.cornerSize) - val background by style.colors.backgroundFor(buttonState) - val border by style.colors.borderFor(buttonState) + val background by style.colors.toggleableBackgroundFor(buttonState) + val border by style.colors.toggleableBorderFor(buttonState) + Box( modifier = Modifier .thenIf(!focusable) { focusProperties { canFocus = false } } .then(modifier) + .toggleable( + onValueChange = onValueChange, + enabled = enabled, + role = Role.Checkbox, + interactionSource = interactionSource, + indication = null, + value = buttonState.toggleableState == ToggleableState.On, + ) + .onActivated(enabled = enabled) { + buttonState = buttonState.copy(active = it) + } .defaultMinSize(style.metrics.minSize.width, style.metrics.minSize.height) .padding(style.metrics.padding) .background(background, shape) @@ -152,24 +230,142 @@ private fun IconButtonImpl( @Immutable @JvmInline -public value class IconButtonState(public val state: ULong) : FocusableComponentState, SelectableComponentState { +public value class IconButtonState(public val state: ULong) : FocusableComponentState { + override val isActive: Boolean + get() = state and Active != 0UL + + override val isEnabled: Boolean + get() = state and Enabled != 0UL + + override val isFocused: Boolean + get() = state and Focused != 0UL + + override val isHovered: Boolean + get() = state and Hovered != 0UL + + override val isPressed: Boolean + get() = state and Pressed != 0UL + + public fun copy( + enabled: Boolean = isEnabled, + focused: Boolean = isFocused, + pressed: Boolean = isPressed, + hovered: Boolean = isHovered, + active: Boolean = isActive, + ): IconButtonState = + of( + enabled = enabled, + focused = focused, + pressed = pressed, + hovered = hovered, + active = active, + ) + + override fun toString(): String = + "${javaClass.simpleName}(isEnabled=$isEnabled, isFocused=$isFocused, isHovered=$isHovered, " + + "isPressed=$isPressed, isActive=$isActive)" + + public companion object { + public fun of( + enabled: Boolean = true, + focused: Boolean = false, + pressed: Boolean = false, + hovered: Boolean = false, + active: Boolean = false, + ): IconButtonState = + IconButtonState( + (if (enabled) Enabled else 0UL) or + (if (focused) Focused else 0UL) or + (if (hovered) Hovered else 0UL) or + (if (pressed) Pressed else 0UL) or + (if (active) Active else 0UL), + ) + } +} + +@Immutable +@JvmInline +public value class ToggleableIconButtonState(public val state: ULong) : FocusableComponentState, ToggleableComponentState { + override val toggleableState: ToggleableState + get() = state.readToggleableState() + + override val isActive: Boolean + get() = state and Active != 0UL + + override val isEnabled: Boolean + get() = state and Enabled != 0UL + + override val isFocused: Boolean + get() = state and Focused != 0UL + + override val isHovered: Boolean + get() = state and Hovered != 0UL + + override val isPressed: Boolean + get() = state and Pressed != 0UL + + public fun copy( + enabled: Boolean = isEnabled, + toggleableState: ToggleableState = this.toggleableState, + focused: Boolean = isFocused, + pressed: Boolean = isPressed, + hovered: Boolean = isHovered, + active: Boolean = isActive, + ): ToggleableIconButtonState = + of( + enabled = enabled, + toggleableState = toggleableState, + focused = focused, + pressed = pressed, + hovered = hovered, + active = active, + ) + + override fun toString(): String = + "${javaClass.simpleName}(isEnabled=$isEnabled, isFocused=$isFocused, isHovered=$isHovered, " + + "isPressed=$isPressed, isActive=$isActive, toggleableState=$toggleableState)" + + public companion object { + public fun of( + enabled: Boolean = true, + toggleableState: ToggleableState = ToggleableState.Off, + focused: Boolean = false, + pressed: Boolean = false, + hovered: Boolean = false, + active: Boolean = false, + ): ToggleableIconButtonState = + ToggleableIconButtonState( + (if (enabled) Enabled else 0UL) or + (if (focused) Focused else 0UL) or + (if (hovered) Hovered else 0UL) or + (if (pressed) Pressed else 0UL) or + (if (active) Active else 0UL) or + (if (toggleableState != ToggleableState.Off) Selected else 0UL) or + (if (toggleableState == ToggleableState.Indeterminate) Indeterminate else 0UL), + ) + } +} + +@Immutable +@JvmInline +public value class SelectableIconButtonState(public val state: ULong) : FocusableComponentState, SelectableComponentState { override val isSelected: Boolean - get() = state and CommonStateBitMask.Selected != 0UL + get() = state and Selected != 0UL override val isActive: Boolean - get() = state and CommonStateBitMask.Active != 0UL + get() = state and Active != 0UL override val isEnabled: Boolean - get() = state and CommonStateBitMask.Enabled != 0UL + get() = state and Enabled != 0UL override val isFocused: Boolean - get() = state and CommonStateBitMask.Focused != 0UL + get() = state and Focused != 0UL override val isHovered: Boolean - get() = state and CommonStateBitMask.Hovered != 0UL + get() = state and Hovered != 0UL override val isPressed: Boolean - get() = state and CommonStateBitMask.Pressed != 0UL + get() = state and Pressed != 0UL public fun copy( enabled: Boolean = isEnabled, @@ -178,7 +374,7 @@ public value class IconButtonState(public val state: ULong) : FocusableComponent pressed: Boolean = isPressed, hovered: Boolean = isHovered, active: Boolean = isActive, - ): IconButtonState = + ): SelectableIconButtonState = of( enabled = enabled, selected = selected, @@ -201,14 +397,14 @@ public value class IconButtonState(public val state: ULong) : FocusableComponent pressed: Boolean = false, hovered: Boolean = false, active: Boolean = false, - ): IconButtonState = - IconButtonState( - (if (enabled) CommonStateBitMask.Enabled else 0UL) or - (if (selected) CommonStateBitMask.Selected else 0UL) or - (if (focused) CommonStateBitMask.Focused else 0UL) or - (if (hovered) CommonStateBitMask.Hovered else 0UL) or - (if (pressed) CommonStateBitMask.Pressed else 0UL) or - (if (active) CommonStateBitMask.Active else 0UL), + ): SelectableIconButtonState = + SelectableIconButtonState( + (if (enabled) Enabled else 0UL) or + (if (selected) Selected else 0UL) or + (if (focused) Focused else 0UL) or + (if (hovered) Hovered else 0UL) or + (if (pressed) Pressed else 0UL) or + (if (active) Active else 0UL), ) } } diff --git a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/IconButtonStyling.kt b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/IconButtonStyling.kt index fdb65deca..8197e5810 100644 --- a/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/IconButtonStyling.kt +++ b/ui/src/main/kotlin/org/jetbrains/jewel/ui/component/styling/IconButtonStyling.kt @@ -10,10 +10,13 @@ import androidx.compose.runtime.State import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.Color +import androidx.compose.ui.state.ToggleableState import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.DpSize import org.jetbrains.jewel.foundation.GenerateDataFunctions import org.jetbrains.jewel.ui.component.IconButtonState +import org.jetbrains.jewel.ui.component.SelectableIconButtonState +import org.jetbrains.jewel.ui.component.ToggleableIconButtonState @Stable @GenerateDataFunctions @@ -44,16 +47,19 @@ public class IconButtonColors( public val borderHovered: Color, ) { @Composable - public fun foregroundFor(state: IconButtonState): State = + public fun backgroundFor(state: IconButtonState): State = rememberUpdatedState( when { - state.isActive && state.isSelected -> foregroundSelectedActivated - else -> Color.Unspecified + !state.isEnabled -> backgroundDisabled + state.isPressed -> backgroundPressed + state.isHovered -> backgroundHovered + state.isFocused -> backgroundFocused + else -> background }, ) @Composable - public fun backgroundFor(state: IconButtonState): State = + public fun selectableBackgroundFor(state: SelectableIconButtonState): State = rememberUpdatedState( when { !state.isEnabled -> backgroundDisabled @@ -66,8 +72,66 @@ public class IconButtonColors( }, ) + @Composable + public fun toggleableBackgroundFor(state: ToggleableIconButtonState): State = + rememberUpdatedState( + when { + !state.isEnabled -> backgroundDisabled + state.isActive && state.isSelected -> backgroundSelectedActivated + state.isSelected -> backgroundSelected + state.isPressed -> backgroundPressed + state.isHovered -> backgroundHovered + state.isFocused -> backgroundFocused + else -> background + }, + ) + + @Composable + public fun selectableForegroundFor(state: SelectableIconButtonState): State = + rememberUpdatedState( + when { + state.isActive && state.isSelected -> foregroundSelectedActivated + else -> Color.Unspecified + }, + ) + + @Composable + public fun toggleableForegroundFor(state: ToggleableIconButtonState): State = + rememberUpdatedState( + when { + state.isActive && state.toggleableState == ToggleableState.On -> foregroundSelectedActivated + else -> Color.Unspecified + }, + ) + @Composable public fun borderFor(state: IconButtonState): State = + rememberUpdatedState( + when { + !state.isEnabled -> borderDisabled + state.isFocused -> borderFocused + state.isPressed -> borderPressed + state.isHovered -> borderHovered + else -> border + }, + ) + + @Composable + public fun selectableBorderFor(state: SelectableIconButtonState): State = + rememberUpdatedState( + when { + !state.isEnabled -> borderDisabled + state.isActive && state.isSelected -> borderSelectedActivated + state.isSelected -> borderSelected + state.isFocused -> borderFocused + state.isPressed -> borderPressed + state.isHovered -> borderHovered + else -> border + }, + ) + + @Composable + public fun toggleableBorderFor(state: ToggleableIconButtonState): State = rememberUpdatedState( when { !state.isEnabled -> borderDisabled