Skip to main content

Module state_layer

Module state_layer 

Source
Expand description

The shared M3 interaction-state overlay: a fill painted over a widget’s shape, in the widget’s own content color, at an opacity selected by its current hover/focus/pressed/dragged interaction state.

Source: androidx Compose Material3 StateTokens (v0_210, retrieval 2026-07-17) — hover 8%, focus 10%, pressed 10%, dragged 16%. This supersedes the older material-web v0.192 table (hover 8%, focus/pressed 12%, dragged 16%); v0_210 is the table this helper ships.

StateLayer carries no theme dependency and no interaction-detection logic of its own — a widget’s own event handler calls the set_* setters to record state transitions (each returns whether the flag actually changed, so a caller can gate a redraw request on it), and its paint calls StateLayer::paint once per frame with the already theme-resolved content color (the 3-tier explicit/theme/fallback precedence stays the caller’s responsibility — see docs/CODE_STANDARDS.md’s Theming conventions).

When more than one state is active at once (e.g. a focused and pressed widget), the overlay opacity is the maximum of the active states’ opacities — M3 does not stack multiple overlays additively, it shows the strongest one.

§Relationship to crate::interaction

This type now wraps crate::interaction::InteractionState and its opacity() method below delegates to InteractionState::max_active_opacity — the exact max-of-active behavior this module has always documented, preserved unchanged for this module’s existing consumers (list_item/card/chips/fab/switch). crate::interaction also hosts a precedence-based resolver (dragged > pressed > focused > hovered, porting the reference’s M3EInteractionState.opacity exactly) as its new documented default for any future consumer — see that module’s doc for why the two resolvers agree numerically on this crate’s own token table today, and why they are still kept as two distinct functions rather than one. This module’s own public constants and StateLayer API are unchanged by that addition; every existing call site here keeps its current behavior.

§Live vs. aspirational states

Of the four interaction states this helper models, pressed and hovered are wired by a shipping widget ([super::list_item], the reference consumer) — the max-of-active-states rule above describes the full M3 design, but two of its four inputs still have no live signal source:

  • hovered — wireable, and wired. There is still no Enter/Leave phase (frust::authoring::PointerPhase remains Down/Move/Up/Cancel), because hover is a claim, not a phase: a widget calls frust::authoring::EventCtx::claim_hover from its uncaptured Move arm once it has hit-tested the pointer inside its own bounds, and the framework records that claim down the pod chain. The claim is per-pass, so the pointer moving anywhere else — including onto a widget this one never hears about — drops the link with no leave event to deliver, which is exactly what a widget could not do for itself before. Three rules bind a consumer: it keeps its own hover flag and gates its request_redraw on StateLayer::set_hovered’s changed-return (the frame source for hover gain — claim_hover asks for none); frust::authoring::PaintCtx::is_hovered is the authoritative read, so the widget re-syncs set_hovered from it every paint (the pointer’s departure never reaches its event); and a claim from a captured pointer is refused by construction, so a drag never tints the widget under the finger. That refusal is structural for a captured pointer only — nothing tells a touch contact from a mouse, so an uncaptured touch drag over a non-capturing consumer does tint, transiently, until the Up at lift ends the link (docs/LIMITATIONS.md’s hover-window-leave-standing).
  • focused — the focus-routing prerequisite landed (material::dialog/ sheet, cupertino::alert_dialog/action_sheet all now call EventCtx::request_focus on a Down and dismiss on a focus-routed Key(Escape)), but still unwired here: none of those four modals paint their own StateLayer-shaped actionable surface — each is a plain scrim + panel modal barrier (fills/hairlines), not an M3 interactive surface with a content color a state layer would tint. There was deliberately nothing to wire set_focused into without inventing chrome no design calls for. A future widget that both participates in focus routing and paints its own StateLayer (e.g. a focusable list item, chip, or button) is what will make this state live.
  • dragged — unwired. No catalog widget reports a drag into StateLayer::set_dragged yet.

The set_focused/set_dragged setters remain the stable API for when those upstream signals exist; they are deliberately kept, not dead-stripped, so a widget can adopt each state the moment its source lands without re-plumbing this helper — which is what let set_hovered be adopted above with no change to this file’s own code.

Re-exports§

pub use crate::interaction::DRAGGED_OPACITY;
pub use crate::interaction::FOCUS_OPACITY;
pub use crate::interaction::HOVER_OPACITY;

Structs§

StateLayer
Tracks a widget’s hover/focus/pressed/dragged interaction state and paints the M3 state-layer overlay for it. See the module docs.

Constants§

PRESSED_OPACITY
Pressed-state overlay opacity (source: androidx Compose Material3 StateTokens v0_210, retrieved 2026-07-17 — supersedes material-web v0.192’s 12%).