Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

State Layer API

The state layer system provides declarative style overrides based on widget interaction state.

Overview

State layers let containers define how they should look when:

  • Hovered - Mouse cursor is over the widget
  • Pressed - Mouse button is held down on the widget

Changes are defined declaratively, and the framework handles state transitions, animations, and rendering.

Basic Usage

#![allow(unused)]
fn main() {
container()
    .background(Color::rgb(0.2, 0.2, 0.3))
    .corner_radius(8.0)
    .hover_state(|s| s.lighter(0.1))      // Style when hovered
    .pressed_state(|s| s.ripple())         // Style when pressed
    .child(text("Click me"))
}

Available Overrides

State layers can override these properties:

Background

#![allow(unused)]
fn main() {
// Explicit color
.hover_state(|s| s.background(Color::rgb(0.4, 0.6, 0.9)))

// Relative to base
.hover_state(|s| s.lighter(0.1))   // 10% lighter
.pressed_state(|s| s.darker(0.1))  // 10% darker
}

Border

#![allow(unused)]
fn main() {
.hover_state(|s| s.border(2.0, Color::WHITE))
.hover_state(|s| s.border_width(2.0))
.hover_state(|s| s.border_color(Color::WHITE))
}

Transform

#![allow(unused)]
fn main() {
.pressed_state(|s| s.transform(Transform::scale(0.98)))
}

Corner Radius

#![allow(unused)]
fn main() {
.hover_state(|s| s.corner_radius(12.0))
}

Elevation

#![allow(unused)]
fn main() {
.hover_state(|s| s.elevation(8.0))
.pressed_state(|s| s.elevation(2.0))
}

Ripple

#![allow(unused)]
fn main() {
.pressed_state(|s| s.ripple())
.pressed_state(|s| s.ripple_with_color(Color::rgba(1.0, 0.8, 0.0, 0.4)))
}

Combining Overrides

Chain multiple overrides in a single state:

#![allow(unused)]
fn main() {
.hover_state(|s| s
    .lighter(0.1)
    .border(2.0, Color::WHITE)
    .elevation(6.0)
)

.pressed_state(|s| s
    .ripple()
    .darker(0.05)
    .transform(Transform::scale(0.98))
)
}

With Animations

Add transitions for smooth state changes:

#![allow(unused)]
fn main() {
container()
    .background(Color::rgb(0.3, 0.5, 0.8))
    .animate_background(Transition::new(200.0, TimingFunction::EaseOut))
    .hover_state(|s| s.lighter(0.15))
    .pressed_state(|s| s.darker(0.1))
}

Complete Example

#![allow(unused)]
fn main() {
fn interactive_button(label: &str) -> Container {
    container()
        .padding(16.0)
        .background(Color::rgb(0.3, 0.5, 0.8))
        .corner_radius(8.0)
        .border(1.0, Color::rgb(0.4, 0.6, 0.9))

        // Animations
        .animate_background(Transition::new(200.0, TimingFunction::EaseOut))
        .animate_border_width(Transition::new(150.0, TimingFunction::EaseOut))
        .animate_transform(Transition::spring(SpringConfig::SMOOTH))

        // State layers
        .hover_state(|s| s
            .lighter(0.1)
            .border(2.0, Color::rgb(0.5, 0.7, 1.0))
        )
        .pressed_state(|s| s
            .ripple()
            .darker(0.05)
            .transform(Transform::scale(0.98))
        )

        .child(text(label).color(Color::WHITE))
}
}

How It Works

Internally, StateStyle holds all possible overrides:

#![allow(unused)]
fn main() {
pub struct StateStyle {
    pub background: Option<BackgroundOverride>,
    pub border_width: Option<f32>,
    pub border_color: Option<Color>,
    pub corner_radius: Option<f32>,
    pub transform: Option<Transform>,
    pub elevation: Option<f32>,
    pub ripple: Option<RippleConfig>,
}
}

When the container paints, it checks the current state and applies overrides accordingly, blending with animations when configured.