guido 0.7.0

A reactive Rust GUI library using wgpu for Wayland layer shell widgets
Documentation
# Transform System

Guido provides a complete 2D transform system for translating, rotating, and scaling widgets.

## Basic Transforms

### Translation

Move a widget by offset values:

```rust
container()
    .translate((20.0, 10.0))  // Move 20px right, 10px down
```

### Relative Translation

A translate can also be a fraction of the widget's own size — CSS's
`translateX(-100%)`:

```rust
container().translate(Translate::relative(-1.0, 0.0))  // one own width left
container().translate(Translate { x: 8.0, ..Translate::relative(-1.0, 0.0) }) // calc(8px - 100%)
```

`Translate` carries four numbers: `x` and `y` in pixels, `relative_x` and
`relative_y` in widths and heights. The fractions are stored as fractions and
resolved with `Translate::resolve` against the container's own laid-out bounds
where the translate is used — in `animated_transform`, which paint and hit
testing share, and in `max_transform_reach`, which sizes what the parent culls
against. That is the pattern `Pivot::percent` already follows.

It is resolved there, and not when the value is declared, because of what has
to animate. The animation interpolates the four numbers component by component,
as CSS interpolates `calc(px + %)`, so a transition or an `entering_from`
between relative offsets never needs a width — which is what lets an
`entering_from` play on the first frame, before layout has measured anything.
And a width change under a relative translate changes no target, so the box is
at its new distance on the frame that lays it out, with nothing animating.

`Transform::compose` takes the translate as a pixel pair rather than a
`Translate`, so an unresolved one does not compile: `Translate::resolve` is how
a `Translate` becomes that pair.

### Rotation

Rotate a widget about its pivot, in degrees, clockwise:

```rust
container()
    .rotate(45.0)  // Rotate 45 degrees clockwise
```

**The angle is not normalised.** `360.0` is a full turn and not zero, `720.0` is
two, and `370.0` is twenty degrees past a full turn rather than ten degrees from
the start. This is what makes a rotation animatable at all: an angle folded into
a matrix, or wrapped into `0..360`, has lost how far round it went, and
interpolating it interpolates something that no longer holds the answer. See
issue #212.

It also means nothing takes a shorter way round on a caller's behalf. An angle
that arrives already wrapped — from `atan2`, in a drag-to-rotate, where the
value jumps from 179 to -179 — wraps the animation with it, and unwrapping it
belongs to the caller, who is the only one who knows which way was meant. A
declared shortest-path policy, as QML's `RotationAnimation.direction` has, is
the shape that would answer it; there is no case for one yet.

### Scale

Scale a widget uniformly or non-uniformly:

```rust
container().scale(1.5)           // 150% size
container().scale((2.0, 0.5))   // 200% width, 50% height
```

## Combining Them

```rust
container().rotate(30.0).scale(0.8)
```

**The order is fixed, not taken from the call site**: translate, then rotate,
then scale, however they are written. Two containers declaring the same three
values are the same shape. Same order, and same reason, as CSS's individual
`translate` / `rotate` / `scale` properties.

## Transform Origin

By default, rotation and scale occur around the widget's center. Use transform origin to change the pivot point:

```rust
// Rotate around top-left corner
container()
    .rotate(45.0)
    .pivot(Pivot::TOP_LEFT)

// Scale from bottom-right
container()
    .scale(0.8)
    .pivot(Pivot::BOTTOM_RIGHT)
```

### Built-in Origins

```rust
Pivot::CENTER        // 50%, 50% (default)
Pivot::TOP_LEFT      // 0%, 0%
Pivot::TOP_RIGHT     // 100%, 0%
Pivot::BOTTOM_LEFT   // 0%, 100%
Pivot::BOTTOM_RIGHT  // 100%, 100%
Pivot::TOP           // 50%, 0%
Pivot::BOTTOM        // 50%, 100%
Pivot::LEFT          // 0%, 50%
Pivot::RIGHT         // 100%, 50%
```

### Custom Origin

```rust
// 25% from left, 75% from top
Pivot::percent(25.0, 75.0)
```

## Reactive Transforms

Transforms can be reactive using signals:

```rust
let rotation = create_signal(0.0f32);

container()
    .rotate(rotation)  // Updates when signal changes
    .on_click(move || rotation.update(|r| *r += 45.0))
```

## Animated Transforms

Animate transform changes with transitions:

```rust
let rotation = create_signal(0.0f32);

container()
    .rotate(rotation.transition(Transition::new(300.0, TimingFunction::EaseOut)))
    .on_click(move || rotation.update(|r| *r += 45.0))
```

### Spring Animation

For physics-based animation:

```rust
container()
    .scale(scale_signal.transition(Transition::spring(SpringConfig::BOUNCY)))
```

## Nested Transforms

Transforms compose through the widget hierarchy. A child inherits its parent's transform:

```rust
container()
    .rotate(20.0)  // Parent rotated
    .child(
        container()
            .scale(0.8)  // Child scaled within rotated parent
            .child(text("Nested transforms"))
    )
```

## Transform in State Layers

Apply transforms on interaction:

```rust
container()
    .when_pressed(|s| s.scale(0.98))
```

## Hit Testing

Transforms are properly accounted for in hit testing. A rotated button will correctly detect clicks within its rotated bounds.

## Transform API Reference

### Transform Struct

Not in `guido::prelude`, and **`Container` does not accept one** — an
application declares the three components and nothing else. This is what they
compose into on the way to the renderer, and it is in `guido::widget_prelude`
for the other job: a widget written outside the crate turning or scaling
*itself*, through `PaintContext::apply_transform` and
`apply_transform_with_pivot`. Placing a child is not that job — that is
`PaintContext::paint_child`, which takes the placement from the tree.

So a shear, or a composition order other than translate → rotate → scale, is
not expressible on a container. Where the old `.transform()` built a rotated
offset with `rotate(5).then(&translate(10, 15))`, the equivalent now is a
nested container: the outer one turns, the inner one moves inside the turned
frame.

```rust
impl Transform {
    // Creation
    pub const IDENTITY: Self;
    pub fn translate(x: f32, y: f32) -> Self;
    pub fn rotate(angle_radians: f32) -> Self;
    pub fn rotate_degrees(angle_degrees: f32) -> Self;
    pub fn scale(s: f32) -> Self;
    pub fn scale_xy(sx: f32, sy: f32) -> Self;

    // Composition
    pub fn then(&self, other: &Transform) -> Transform;
    pub fn center_at(self, cx: f32, cy: f32) -> Self;

    // The matrix itself, `[a, b, tx, c, d, ty]`
    pub fn a(&self) -> f32;  // and b, c, d, tx, ty

    // Utilities
    pub fn inverse(&self) -> Transform;
    pub fn transform_point(&self, x: f32, y: f32) -> (f32, f32);
    pub fn is_identity(&self) -> bool;
    pub fn is_translation_only(&self) -> bool;
    pub fn is_pure_translation(&self) -> bool;
    pub fn keeps_axes(&self) -> bool;
    pub fn rect_stays_rect(&self) -> bool;
    pub fn corner_permutation(&self) -> [usize; 4];
}
```

The three predicates answer different questions and are not interchangeable.
`is_translation_only` is epsilon'd and guards the paint cache's translate-only
replay; `is_pure_translation` is exact and guards undoing a placement by
negating two components. `keeps_axes` asks whether a rect stays a rect *with its
corners in the order the radii are written in* — so it refuses a quarter turn
and a mirror, which do keep the rect but move the top-left corner elsewhere. It
decides whether a fast path that assumes upright may run. `rect_stays_rect` is
the wider question — does a rect stay a rect, however its corners move — and is
what decides whether two clips can share one coordinate space, paired with
`corner_permutation` to carry the radii across.

### Container Transform Methods

```rust
impl Container {
    pub fn translate<M>(self, t: impl IntoAnimated<Translate, M>) -> Self;
    pub fn rotate<M>(self, degrees: impl IntoAnimated<f32, M>) -> Self;
    pub fn scale<M>(self, factor: impl IntoAnimated<Scale, M>) -> Self;
    pub fn pivot<M>(self, pivot: impl IntoSignal<Pivot, M>) -> Self;
}
```