Skip to content

Commit

Permalink
Add selectable and toggleable Icon(Action)Button (#501)
Browse files Browse the repository at this point in the history
Introduces two variants for `IconButton` and `IconActionButton`:
* `SelectableIcon(Action)Button`
* `ToggleableIcon(Action)Button`

What they do should be self-evident.

Other changes include adding the ability to provide `PainterHint`s to
`IconActionButton`s, adding a standalone demo for the new icon buttons,
separating and cleaning out states and styling helpers.

Note: the main difference between an `IconButton` and its equivalent
`IconActionButton` is that the latter is built upon the former, and
handles automatically:
 * Tooltips (if defined)
 * Stroke modifiers (for selectable and toggleable)
 * Icon loading
  • Loading branch information
rock3r authored Jul 30, 2024
1 parent c422bee commit dae048c
Show file tree
Hide file tree
Showing 10 changed files with 691 additions and 117 deletions.
7 changes: 6 additions & 1 deletion foundation/api/foundation.api
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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") },
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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)),
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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()
}
}
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand All @@ -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 },
)
}
}

Expand Down
Loading

0 comments on commit dae048c

Please sign in to comment.