# Ripple Effects
Ripples provide Material Design-style touch feedback. They expand from the click point and create a visual acknowledgment of user interaction.
## Basic Ripple
Add a default ripple to the pressed state:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
container()
.background(Color::rgb(0.2, 0.2, 0.3))
.corners(8.0)
.when_pressed(|s| s.ripple())
# ;
# }
```
The default ripple uses a semi-transparent white overlay.
## Colored Ripple
Customize the ripple color:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# container()
.when_pressed(|s| s.ripple_with_color(Color::rgba(1.0, 0.8, 0.0, 0.4)))
# ;
# }
```
Good ripple colors have transparency (alpha 0.2-0.5):
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
// Subtle white
Color::rgba(1.0, 1.0, 1.0, 0.2);
// Yellow accent
Color::rgba(1.0, 0.8, 0.0, 0.4);
// Blue accent
Color::rgba(0.3, 0.5, 1.0, 0.3)
# ;
# }
```
## Ripple with Other Effects
Combine ripples with other pressed state changes:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# container()
.when_pressed(|s| s
.ripple()
.darker(0.05)
.scale(0.98)
)
# ;
# }
```
## How Ripples Work
1. **Press** - a disc appears at the click point, already at about a third of
its final size, and starts spreading
2. **Release inside** - the press happened, so the remaining expansion is
*completed* rather than interrupted, while the disc fades out
3. **Release elsewhere, or drag off the button** - nothing was activated, and
the ripple says the same thing `on_click` does: the disc just fades, quickly
Two properties are worth stating outright, because they are what makes the
effect read as an acknowledgement rather than a hesitation:
- **The radius never goes backwards.** The exit is a fade, never a contraction.
- **A short press does not truncate the expansion.** A click lasts 60-150ms
against a growth measured in hundreds, so the release finishes the growth
instead of abandoning it half-way.
- **No press is dimmer for being quicker.** The disc always finishes appearing
before it starts to leave, so a 20ms tap is as bright as a held one.
The disc's centre also drifts toward the container's own centre as it grows, so
a press near a corner settles onto the button instead of staying lopsided. Its
final size comes from the container, not from where you pressed — every ripple
on a given button is the same size, and a full one covers it exactly.
The ripple:
- Respects corner radius and container shape
- Works correctly with transformed containers (rotated, scaled)
- Renders in the overlay layer (on top of content)
## Several at once
Each press is its own ripple, and they overlap:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let increment = move || {};
container()
.when_pressed(|s| s.ripple())
.on_click(increment)
# ;
# }
```
Clicking this repeatedly layers one disc over the next, because two clicks are
two events — the second does not erase the first. Up to four are alive at a
time; past that the oldest is dropped, which is the one furthest through its
own fade.
## Timing
| Opacity rise on contact | 75ms | `fade_speed` |
| Growth while held | 1s | `expand_speed` |
| Growth remaining after release | 225ms | `expand_speed` |
| Fade after a release | 375ms | `fade_speed` |
| Fade after an abandoned press | 75ms | `fade_speed` |
The fade never starts before the rise has finished, and the growth after a
release is never given longer than the fade it overlaps — so no combination of
the two speeds can put the disc back where it started, half-grown and already
invisible.
Both speeds are clamped to a sane range, so a zero or a negative one degrades
instead of stalling the frame loop.
The colour is a `Signal<Color>`, as every other declared value is, so it is
built through `RippleConfig::with_color` — which takes a value, a closure or a
signal — rather than assigned as a bare `Color`. The two speeds stay plain
numbers:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# container()
.when_pressed(|s| s.ripple_config(RippleConfig {
expand_speed: 1.5, // faster growth
fade_speed: 1.0,
..RippleConfig::with_color(Color::rgba(1.0, 1.0, 1.0, 0.3))
}))
# ;
# }
```
## Ripples on Transformed Containers
Ripples work correctly even with transforms:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
container()
.padding(16.0)
.background(Color::rgb(0.4, 0.6, 0.4))
.corners(8.0)
// Fixed order: the offset is in the parent's frame, and the box turns
// where it has been moved to.
.translate((10.0, 15.0))
.rotate(5.0)
.when_hovered(|s| s.lighter(0.1))
.when_pressed(|s| s.ripple())
# ;
# }
```
Click coordinates are properly transformed to local container space.
## Ripples with Corner Curvature
Ripples respect different corner styles:
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
// Squircle ripple
container()
.corners(Corners::squircle(12.0))
.when_pressed(|s| s.ripple());
// Beveled ripple
container()
.corners(Corners::bevel(12.0))
.when_pressed(|s| s.ripple())
# ;
# }
```
## Complete Example
```rust
# extern crate guido;
# use guido::prelude::*;
fn ripple_button(label: &str, color: Color) -> Container {
container()
.padding(16.0)
.background(color)
.corners(8.0)
// Subtle hover, ripple on press
.when_hovered(|s| s.lighter(0.1))
.when_pressed(|s| s.ripple().scale(0.98))
.on_click(|| println!("Clicked!"))
.child(container().child(text(label).color(Color::WHITE)))
}
# fn main() {
// Usage
ripple_button("Default Ripple", Color::rgb(0.3, 0.5, 0.8));
ripple_button("Action Button", Color::rgb(0.8, 0.3, 0.3));
# }
```
## Ripple Color Guidelines
| Dark | `Color::rgba(1.0, 1.0, 1.0, 0.2-0.3)` |
| Light | `Color::rgba(0.0, 0.0, 0.0, 0.1-0.2)` |
| Colored | Lighter tint with 0.3-0.4 alpha |
## API Reference
```rust
# extern crate guido;
# use guido::prelude::*;
# fn main() {
# let a = 1.0f32;
# let b = 0.5f32;
# let g = 0.5f32;
# let r = 0.5f32;
# container()
// Default semi-transparent white ripple
.when_pressed(|s| s.ripple())
// Custom colored ripple
.when_pressed(|s| s.ripple_with_color(Color::rgba(r, g, b, a)))
# ;
# }
```