# 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
```
### 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;
}
```