guido 0.12.0

A reactive Rust GUI library using wgpu for Wayland layer shell widgets
Documentation
# Spring Physics

Spring animations use physics simulation for natural, dynamic motion. Unlike duration-based transitions, springs can overshoot and bounce.

## Creating Spring Transitions

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
Transition::spring(SpringConfig::BOUNCY)
# ;
# }
```

## Built-in Configurations

### DEFAULT

Balanced spring - responsive without excessive bounce, settles in ~270ms:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
SpringConfig::DEFAULT
# ;
# }
```

### SNAPPY

Quickest response with subtle overshoot, settles in ~185ms:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
SpringConfig::SNAPPY
# ;
# }
```

### GENTLE

Slower and smooth - minimal overshoot, settles in ~325ms:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
SpringConfig::GENTLE
# ;
# }
```

### BOUNCY

Energetic spring - visible bounce, settles in ~410ms:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
SpringConfig::BOUNCY
# ;
# }
```

## When to Use Springs

Springs excel at:

- **Width/height animations** - Expanding cards, accordions
- **Transform animations** - Scale, rotation, translation
- **Physical feedback** - Elements that should feel tangible

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
// Width expansion with spring
let expanded = create_signal(false);

container()
    .width(
        (move || if expanded.get() { 600.0 } else { 50.0 })
            .transition(Transition::spring(SpringConfig::DEFAULT)),
    )
    .on_click(move || expanded.update(|e| *e = !*e))
# ;
# }
```

## Spring vs Duration

### Use Duration For:
- Color changes
- Opacity changes
- Border changes
- Subtle state transitions

### Use Springs For:
- Size changes
- Position changes
- Scale transforms
- Anything that should feel physical

## Examples

### Bouncy Scale

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
let scale_factor = create_signal(1.0f32);
let is_scaled = create_signal(false);

container()
    .scale(scale_factor.transition(Transition::spring(SpringConfig::BOUNCY)))
    .on_click(move || {
        is_scaled.update(|s| *s = !*s);
        let target = if is_scaled.get() { 1.3 } else { 1.0 };
        scale_factor.set(target);
    })
# ;
# }
```

### Smooth Expansion

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
let expanded = create_signal(false);

container()
    .width(
        (move || at_least(if expanded.get() { 400.0 } else { 200.0 }))
            .transition(Transition::spring(SpringConfig::GENTLE)),
    )
    .on_click(move || expanded.update(|e| *e = !*e))
# ;
# }
```

### Animated Rotation

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
let rotation = create_signal(0.0f32);

container()
    .rotate(rotation.transition(Transition::spring(SpringConfig::DEFAULT)))
    .on_click(move || rotation.update(|r| *r += 90.0))
# ;
# }
```

## Combining Spring and Duration

Use different transition types for different properties:

```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
container()
    // Spring for physical properties
    .rotate(0.0.transition(Transition::spring(SpringConfig::BOUNCY)))
    .width(200.0.transition(Transition::spring(SpringConfig::GENTLE)))

    // Duration for color properties
    .background(Color::WHITE.transition(Transition::new(200.0, TimingFunction::EaseOut)))
    .border(1.0, Color::WHITE.transition(Transition::new(150.0, TimingFunction::EaseOut)))
# ;
# }
```

## Complete Example

```rust
# extern crate guido;
# use guido::prelude::*;
fn spring_button() -> Container {
    let pressed = create_signal(false);

    container()
        .padding(20.0)
        // Duration for color - smooth transition
        .background(Color::rgb(0.3, 0.5, 0.8).transition(200.0))
        .corners(12.0)

        // Spring for scale - bouncy feedback
        .scale(
            (move || if pressed.get() { 1.1 } else { 1.0 })
                .transition(Transition::spring(SpringConfig::BOUNCY)),
        )

        .when_hovered(|s| s.lighter(0.1))
        .on_click(move || {
            pressed.set(true);
            // In real app: trigger action and reset
        })

        .child(container().child(text("Spring!").font_size(18.0).color(Color::WHITE)))
}
# fn main() {}
```

## Interruption

A spring that is retargeted mid-flight keeps its momentum: the new segment
starts with the speed the old one had, so a value sent back where it came from
*turns around* rather than stopping first. That is what makes a spring feel
physical when a hover reverses halfway, and it is also the cheapest wobble
there is — send a value out and then back, and the overshoot on the way home
is a shake nobody had to choreograph.

It has to be two *different* frames, though. A press and its release are two,
so a button shakes itself:

```rust
# extern crate guido;
# use guido::prelude::*;
fn nudge(angle: RwSignal<f32>) -> Container {
    container()
        .rotate((move || angle.get()).transition(Transition::spring(SpringConfig::BOUNCY)))
        .on_mouse_down(move |_, _| angle.set(2.0))
        .on_mouse_up(move |_, _| angle.set(0.0))
        .child(text("shake me"))
}
# fn main() {}
```

Two writes inside one frame would not do it: the loop reads the signal once, so
it only ever sees the last of them and the spring is never sent anywhere.

Timed transitions have no momentum to keep: a retarget there starts from the
current value at whatever the curve says, as it always has.

### What it carries, and what it does not

The momentum that crosses into the new segment is the part of the old motion
pointing along it. A translation interrupted by a pure scale change carries
nothing, because the two have no direction in common — momentum stays in the
channel it belonged to. And a spring that has already settled is at rest, so
the next animation starts from a stop however small the residue was.

## API Reference

```rust,ignore
# // not compiled: a signature listing — these declarations have no bodies.
/// Spring configuration presets
pub struct SpringConfig {
    pub stiffness: f32,
    pub damping: f32,
    pub mass: f32,
}

impl SpringConfig {
    pub const DEFAULT: SpringConfig;
    pub const GENTLE: SpringConfig;
    pub const SNAPPY: SpringConfig;
    pub const BOUNCY: SpringConfig;
}

/// Create spring transition
Transition::spring(config: SpringConfig) -> Transition
```