Guides
Transforms
Visual translation, scaling and rotation of any element, which the pointer, text input and accessibility follow.
Translate, Scale, and Rotate change an element and everything inside
it visually, while layout reserves its original space. They work on boxes,
text, images, controls, and custom drawings. The pointer follows what is
drawn, including nested transforms and clips:
ui.Column(c).Size(180, 110).Padding(12).Gap(8).
Scale(1.1, 0.9).Rotate(12).Translate(40, 0).
Children(func() {
ui.Text(c, "Transformed controls")
ui.TextInput(c, &app.name)
})Transforms in a method chain act in call order: this example scales, then
rotates, then translates. A parent's transform acts after its children's.
Each scale and rotation uses the center of the element's border box by
default. TransformOrigin(0, 0) uses its top-left; (1, 1) its bottom-right.
Other fractions, including values outside the box, are allowed. Negative
scales reflect content; a zero scale makes the element invisible and gives
it no pointer hit region. Transform values must be finite.
For reusable compositions, build a Transform and set it with Transform:
pose := ui.Scaling(1.2, 1.2).Then(ui.Rotation(15)).Then(ui.Translation(30, 0))
ui.Box(c).Transform(pose).TransformOrigin(0, 0)The zero Transform is identity. Setting Transform replaces the element's
previous transform, TranslatePercent's too; the convenience methods
append to it.
TranslatePercent(x, y) moves an element by percents of its own width and
height, as CSS's translate(-50%, -50%): of its border box as the frame
lays it out, so that it is in place from its first frame, without waiting
for its Bounds, and follows its size in the frame it changes. It acts
after the element's other transforms, wherever it is called among them,
as CSS's translate property does, and before its pose; calls add up. A
callout centered above the point it points at, whatever its size:
ui.Box(c).Absolute().Left(at.X).Top(at.Y - 8).TranslatePercent(-50, -100).Children(callout)A transition's Motion and a Pose move by percents of the element's
size too (XPercent, YPercent). Set transforms
in the view, before the frame is laid out. Rotate on an icon keeps its
existing behavior of rotating the SVG artwork around its center. To rotate
an icon's whole border box, use Transform(ui.Rotation(degrees)).
Skew and 3D
Skew(x, y) slants an element by degrees along each axis, as CSS's
skew(). RotateX and RotateY turn it in 3D around the horizontal and
vertical axes through its origin, and Perspective(distance) after them
sees it from that many DIPs in front: the shorter, the deeper; 800 or so
looks natural. Without Perspective, a turned element only looks narrower
or shorter:
// A card turning over: its back, turned half a turn more, shows past
// 90 degrees, and each face hides while it is turned away.
card := ui.Box(c).Size(160, 100)
turn := float32(0)
if app.flipped {
turn = 180
}
turn = card.AnimateTo("turn", turn, ui.Snappy)
card.Children(func() {
front := ui.Box(c).Background(t.Surface)
back := ui.Box(c).Background(t.Accent)
front.Absolute().Fill().RotateY(turn).Perspective(800).BackfaceHidden()
back.Absolute().Fill().RotateY(turn + 180).Perspective(800).BackfaceHidden()
})TranslateZ(z) moves an element along Z, toward the viewer for positive
values and away for negative ones, as CSS's translateZ(). Seen in
Perspective(distance) after it, it looks distance/(distance-z) its
size, about its origin: 120 DIPs back seen from 800, 800/920 its size.
Without Perspective, a move along Z shows nothing, as in CSS; one as far
as the viewer, or past it, puts the element behind the viewer, where it
shows nothing and takes no pointer input. Before a turn, it pushes an
element out from the axis, as the items of a carousel; after it, it
lifts the turned element toward the viewer:
// A carousel: each item out on a ring of 300 DIPs, turned by spin, the
// ring's center 300 behind the plane so that the item in front shows at
// its size. Those farther round look smaller, and each hides while the
// viewer sees its back.
for i, item := range app.items {
angle := spin + float32(i)*360/float32(len(app.items))
ring := ui.TranslationZ(300).Then(ui.RotationY(angle)).Then(ui.TranslationZ(-300)).Then(ui.Perspective(800))
ui.Box(c).Absolute().Left(200).Top(40).Size(160, 100).Background(item.color).
Transform(ring).BackfaceHidden()
}
// A stack in depth: each card 60 DIPs behind the one in front of it,
// the farthest drawn first.
for i := 2; i >= 0; i-- {
ui.Box(c).Absolute().Fill().Radius(12).Background(t.Surface).Border(1, t.Border).
TranslateZ(-60 * float32(i)).Perspective(800)
}Elements moved along Z still draw in their order, not by depth, as each flattens into its parent's plane: draw the nearest last.
Transform values compose the same way, with Skewing, TranslationZ,
Translation3D(x, y, z) (CSS's translate3d()), RotationX,
RotationY and Perspective. Each element flattens what its 3D transform
makes of it into its parent's plane, as CSS's default transform-style: flat: a parent turning in 3D turns its children's drawing with it.
BackfaceHidden hides an element, and takes it from the pointer, while
its own 3D transform turns its back to the viewer: from where the viewer
sees it in perspective, as browsers have it, so that an item pushed out
along Z turns its back as it passes the viewer's line of sight.
The pointer, clips, text input and accessibility follow 3D transforms as they follow 2D ones, and the renderers draw them in perspective, smooth edges included.
Coordinates, clips, and overlays
Bounds() is the previous frame's transformed bounding rectangle in
window DIPs. LayoutBounds() is its box before transforms. LocalToWindow
and WindowToLocal convert points relative to that box; WindowToLocal
reports false when the transform has no inverse. PointerPosition,
Dragged, and HandleInput report local positions and vectors. Captured
pointer input stays local even outside the transformed box.
Draw and DrawOver receive the original layout coordinates, as before:
the painter applies the transform to everything they draw, including paths,
text, images, shadows, and effects. Clips retain the coordinate system in
which they were pushed and compose through nested elements. IME candidate
windows and accessibility receive the transformed bounding rectangles of
carets and elements. Popovers stay upright in the overlay and attach to
the transformed bounds of their anchors, including during layout transitions.
Animating transforms
Transforms take animated values as well as fixed ones, Z's included: a spring that keeps its velocity, a curve, keyframes, or a value following the pointer (see Motion):
tile := ui.Box(c).Key("tile").Size(80, 50)
target := float32(0)
if app.expanded {
target = 120
}
tile.Translate(tile.AnimateTo("x", target, ui.Snappy), 0)Transitions scale and rotate elements coming and going,
and move an element appearing from where another showed (Match), with
transforms too.
Rendering
Metal, Direct3D 11, and OpenGL apply general transforms to their instanced geometry on the GPU. Fills, borders, shadows, images, text, and effects keep local coordinates; backdrop reads map back into window coordinates. Metal preserves wide-gamut colors under transforms.
Nested rounded or transformed clips use retained coverage masks once content inside them is scaled, rotated, skewed or in perspective. Content that a transform only moves, such as a label translated by a fraction of a pixel, needs no mask: its clips are cut as for content that isn't transformed. A clip that reaches into the rounded corners of a clip around it, such as a clipped column filling the bottom of a rounded card, uses the rounded clip's mask too, whatever its content. Moving content under a stationary clip reuses that mask without CPU pixel work. Stationary clips that reduce to one rounded rectangle need no mask, even around scaled, rotated or perspective content. This includes a clipped sidebar or header against the sides of a rounded app frame, and a fractional rectangular viewport that removes whole corners. Overlapping antialiased edges and image or gradient masks keep their existing mask path. Hard clips sharing their content's coordinates, including transformed text inputs, are evaluated directly in the fragment shader and reuse their ancestor's mask. Changing a complex clip recomputes its coverage; resizing it replaces its texture in place rather than accumulating cached images.
The CPU compositor remains the renderer for machines without a GPU and for
small resting updates. Active transformed motion uses the GPU where one is
available. Text bitmaps are sampled bilinearly when transformed, and
transformed text uses grayscale coverage. GPU antialiasing follows the CPU
renderer in device-pixel coordinates. To zoom a whole window, sharp, use
the content's zoom (Context.SetZoom, see
Windows) rather than a transform.
The gallery's Motion page shows nested transformed editable controls, an anchored popover, springs retargeted and thrown, keyframe sequences, and shared elements.