Transform Basics
Learn the fundamental transform operations: translate, rotate, and scale.
Translation
Move a widget by offset values:
extern crate guido;
use guido::prelude::*;
fn main() {
container()
.translate((20.0, 10.0)) // Move 20px right, 10px down
;
}
Negative values move in the opposite direction:
extern crate guido;
use guido::prelude::*;
fn main() {
container()
.translate((-10.0, 0.0)) // Move 10px left
;
}
By Its Own Size
Translate::relative moves a widget by fractions of its own size — CSS’s
translateX(-100%). -1.0 is one of its own widths:
extern crate guido;
use guido::prelude::*;
fn main() {
container()
.translate(Translate::relative(-1.0, 0.0)) // exactly its own width left
;
}
The fraction is resolved against the size the container is laid out at, every
time it is painted, so it stays one width at any width — including one that
changes, and including the first frame, before anything has been measured.
Pixels and a fraction add, like CSS’s calc(8px - 100%):
extern crate guido;
use guido::prelude::*;
fn main() {
container()
.translate(Translate { x: 8.0, ..Translate::relative(-1.0, 0.0) })
;
}
Reading the width back through a WidgetRef and
translating by it is the other way to spell this, and the wrong one for a
slide: the width is still zero when an entering_from plays, and a width
change under a transition animates an offset that never changed. See
animated transforms.
Rotation
Rotate a widget around its center (default):
extern crate guido;
use guido::prelude::*;
fn main() {
container()
.rotate(45.0) // Rotate 45 degrees clockwise
;
}
Degrees, always, and the number is kept as written. A full turn is 360.0 and
not 0.0, two turns are 720.0, and nothing is normalised behind your back:
extern crate guido;
use guido::prelude::*;
fn main() {
container().rotate(360.0); // one full turn — animates as one
container().rotate(-90.0) // a quarter turn anticlockwise
;
}
That matters as soon as the angle is animated. See animated transforms.
Scale
Uniform Scale
Scale equally in both dimensions:
extern crate guido;
use guido::prelude::*;
fn main() {
container().scale(1.5); // 150% size
container().scale(0.8) // 80% size
;
}
Non-Uniform Scale
Scale differently on each axis:
extern crate guido;
use guido::prelude::*;
fn main() {
container().scale((2.0, 0.5)) // 200% width, 50% height
;
}
Combining Them
Declare as many as you need on the same container. They apply in one fixed order — translate, then rotate, then scale — whatever order you write them in:
extern crate guido;
use guido::prelude::*;
fn main() {
container()
.rotate(30.0)
.translate((50.0, 0.0))
.scale(1.2)
;
}
The order is fixed rather than taken from the call site so that two containers
that declare the same three values are the same shape. It is the order CSS uses
for its translate / rotate / scale properties, for the same reason.
Rotation and scale both happen about the pivot, which is the
centre of the container unless you say otherwise — so rotating about a corner is
.pivot(Pivot::TOP_LEFT), not a translate-rotate-translate sandwich.
Reactive Transforms
Transforms can use signals for dynamic updates:
extern crate guido;
use guido::prelude::*;
fn main() {
let rotation = create_signal(0.0f32);
container()
.rotate(rotation)
.on_click(move || rotation.update(|r| *r += 45.0))
;
}
When the signal changes, the container repaints. Nothing re-runs layout: all three are paint-only, so the space the container was given does not move and neither does anything around it.
Complete Example
extern crate guido;
use guido::prelude::*;
fn transform_demo() -> impl Widget {
let rotation = create_signal(0.0f32);
let scale_factor = create_signal(1.0f32);
container()
.layout(Flex::row().spacing(20.0))
.children([
// Static rotation
container()
.width(60.0)
.height(60.0)
.background(Color::rgb(0.8, 0.3, 0.3))
.corners(8.0)
.rotate(45.0)
.child(container().child(text("45°").color(Color::WHITE))),
// Click to rotate
container()
.width(60.0)
.height(60.0)
.background(Color::rgb(0.3, 0.6, 0.8))
.corners(8.0)
.rotate(rotation)
.when_hovered(|s| s.lighter(0.1))
.on_click(move || rotation.update(|r| *r += 45.0))
.child(container().child(text("Click").color(Color::WHITE))),
// Click to scale
container()
.width(60.0)
.height(60.0)
.background(Color::rgb(0.3, 0.8, 0.4))
.corners(8.0)
.scale(scale_factor)
.when_hovered(|s| s.lighter(0.1))
.on_click(move || {
let new = if scale_factor.get() > 1.0 { 1.0 } else { 1.3 };
scale_factor.set(new);
})
.child(container().child(text("Scale").color(Color::WHITE))),
])
}
fn main() {}
API Reference
Container Methods
All transform properties accept static values, signals, or closures. Integers also work (e.g., .rotate(45), .scale(2)).
// not compiled: a signature listing — these declarations have no bodies.
impl Container {
pub fn translate<M>(self, t: impl IntoSignal<Translate, M>) -> Self;
pub fn rotate<M>(self, degrees: impl IntoSignal<f32, M>) -> Self;
pub fn scale<M>(self, factor: impl IntoSignal<Scale, M>) -> Self;
pub fn pivot<M>(self, pivot: impl IntoSignal<Pivot, M>) -> Self;
}
The Value Types
A pair builds either one; a bare number builds a uniform Scale.
extern crate guido;
use guido::prelude::*;
fn main() {
Translate::NONE; // no displacement
Translate::new(20.0, 10.0); // or just (20.0, 10.0)
Translate::relative(-1.0, 0.0); // one of its own widths left
Scale::NONE; // unscaled — 1.0 on both axes
Scale::uniform(1.5); // or just 1.5
Scale::new(2.0, 0.5) // or just (2.0, 0.5)
;
}
There is no matrix in the application-facing API. The three components are what
an application declares, and a matrix is what they compose into on the way to
the renderer — a widget written outside the crate reaches it through
guido::widget_prelude.