WidgetState¶
object WidgetState
Resolves a stateful composable's TextureStates key from an ordered set of independent boolean state axes (hovered, checked, pressed, ...), replacing the hand-written when chain every stateful composable (Checkbox, Radio, Switch, Button, Slider, Tab) used to maintain separately - which had drifted out of sync with each other (e.g. Tab's combined hover state only fired via its selected axis, not its pressed axis, unlike every other component - see resolve's "Tab" note).
Example¶
Types¶
Axis¶
One named boolean axis of a widget's interaction state (e.g. "focused" paired with whether the widget currently is), in the priority order resolve should consider it - pass axes to resolve most-significant first (typically the "activated" axis - checked/ selected/pressed - before "focused").
Functions¶
clicked¶
fun clicked(active: Boolean): WidgetState.Axis
An Axis for TextureStates.CLICKED - a checkbox/switch/radio's checked-or-selected state, a button/tab's pressed-or-selected state, or a slider's dragging state.
focused¶
fun focused(active: Boolean): WidgetState.Axis
An Axis for TextureStates.FOCUSED - whether the widget is currently highlighted, meaning either the mouse is hovering it or it holds vanilla keyboard/controller focus (see Modifier.focusable). Both drive the same texture state: there's one "this is the thing about to be interacted with" visual regardless of which input method put it there, so callers that support both pass a single merged boolean (e.g. isHovered || isFocused) rather than two independent axes.
resolve¶
fun resolve(
theme: ComposableTheme,
variant: String,
vararg axes: WidgetState.Axis,
enabled: Boolean = true
): String
Resolves the TextureStates key for theme/variant given enabled and axes (in descending priority order - see Axis).
-
If
enabledisfalse, returnsTextureStates.DISABLEDifthemedefines it forvariant(viaComposableTheme.hasState), otherwise falls through as if disabled weren't a factor - matching every existing chain's behavior of only branching on!enabledwhere adisabledtheme state actually exists to show. -
Otherwise, tries the most specific composite key first: every currently-active axis's
Axis.name, joined by"_and_"in priority order (e.g."clicked_and_focused"forclicked+focusedboth active). Ifthemedoesn't define that combination, falls back one axis at a time - by priority, i.e. trying each individual active axis's own key alone, highest priority first - stopping at the first onethemedefines. -
Returns
TextureStates.DEFAULTif no active axis (alone or combined) has a defined state, or if no axis is active at all.
This graceful per-axis fallback (rather than jumping straight from the full composite to TextureStates.DEFAULT) generalizes what Button's chain alone used to do by hand (falling through a missing "clicked" state to "focused" - button.json defines no "clicked" state at all) - every caller gets it for free, without needing its own hasState check.
Tab note: TabContainer.kt's old chain computed its "clicked" axis from selected || isPressed, but only paired it with focused into the combined state when specifically selected was true - a pressed-but-unselected-and-hovered tab silently lost its hover visual. Callers migrating to this resolver should pass a single clicked axis (selected || isPressed) and a separate focused axis as normal; resolve then treats both uniformly like every other component, which is a deliberate behavior fix, not an incidental one.