Animations
Window animations are USS transitions. They are in Assets/UIToolbox/Runtime/Resources/UIToolbox/Styles/WindowAnimations.uss.
The AnimationsDemo example scene shows each kind of animation on this page, with its code: presets, custom presets, AnimateBinding, your own transitions and restyled window animations.
There are two kinds of classes:
- A transition class holds only
transition-*properties. It is on the chrome while the animation plays. - A state class holds the animated values.
Constant (WindowAnimations) |
Class | Kind | Default |
|---|---|---|---|
WINDOW_OPEN |
tb-window--anim-open |
Transition | opacity, scale, normal duration, entrance easing. |
WINDOW_CLOSE |
tb-window--anim-close |
Transition | opacity, scale, fast duration, exit easing. |
WINDOW_MINIMIZE |
tb-window--anim-minimize |
Transition | height, fast duration, exit easing. |
WINDOW_MAXIMIZE |
tb-window--anim-maximize |
Transition | left, top, width, height, fast duration. |
WINDOW_RESTORE |
tb-window--anim-restore |
Transition | Same as maximize. |
WINDOW_CLOSED |
tb-window--closed |
State | opacity: 0; scale: 0.9 0.9. Open starts here and close ends here. |
WINDOW_MINIMIZED |
tb-window--minimized |
State | Hides tb-window-content. C# sets the chrome height. |
Maximize, restore and minimize animate bounds that C# writes (left, top, width, height).
If Workspace.AnimationsEnabled is false (animations-enabled="false"), no transition plays. The changes apply at once.
Restyle a built-in animation
Put the override in a user sheet or a theme. Two ways:
- Change the
--tb-animation-*variables. This changes all animations. - Override the classes. This changes one animation.
Example: windows slide up when they open.
.tb-window--closed {
opacity: 0;
scale: 1 1;
translate: 0 24px;
}
.tb-window--anim-open,
.tb-window--anim-close {
transition-property: opacity, translate;
}
To turn off one animation, set its duration to zero:
.tb-window--anim-minimize {
transition-duration: 0s;
}
Add an animation
Use WindowAnimations.PlayAsync or WindowAnimations.EnterAsync on your own elements.
| Method | Use |
|---|---|
PlayAsync(element, transitionClass, change, cancellationToken) |
The element is already in a panel and its style is resolved. |
EnterAsync(element, transitionClass, change, cancellationToken) |
The element was just added to a panel. It waits one style pass before change runs, because UI Toolkit does not transition the first resolved style. |
Both methods:
- Add
transitionClassto the element. - Run
change(for example, add or remove a state class). - Complete when the transitions that the change started end or are cancelled. A fallback timer (longest duration plus delay plus 100 ms) completes the task if no end event arrives.
- Remove
transitionClass.
The task completes at once, and the change still applies, if the element is not in a panel, the token is already cancelled, or transitionClass is empty.
USS:
.my-panel--hidden {
opacity: 0;
translate: 0 -12px;
}
.my-panel--anim-show {
transition-property: opacity, translate;
transition-duration: var(--tb-animation-duration-normal);
transition-timing-function: var(--tb-animation-easing-entrance);
}
C#:
using System.Threading.Tasks;
using UIToolbox.Animation;
using UnityEngine.UIElements;
public static class PanelAnimations
{
public static Task ShowAsync(VisualElement panel)
{
panel.AddToClassList("my-panel--hidden");
return WindowAnimations.EnterAsync(panel, "my-panel--anim-show",
() => panel.RemoveFromClassList("my-panel--hidden"));
}
}
Animation presets
A preset is a named animation for any element. Play it from C# or from UXML, with no transition code.
await WindowAnimations.PlayPresetAsync(goldLabel, "flash");
_ = WindowAnimations.PlayPresetAsync(healthBar, "pulse", loop: true); // until StopPreset
await WindowAnimations.PlayPresetAsync(panel, "fade-out", keep: true); // stays hidden until StopPreset
WindowAnimations.StopPreset(healthBar, "pulse");
<ui:Label>
<Bindings>
<ui:DataBinding property="text" data-source-path="Gold" binding-mode="ToTarget" />
<tb:AnimateBinding property="anim-gold" preset="flash" path="Gold" />
</Bindings>
</ui:Label>
AnimateBinding watches the value at path, from the data source of the element. property is only a unique id, because the binding writes nothing. trigger sets when the preset plays:
trigger |
Plays |
|---|---|
Change (default) |
Each time the value changes. The first value does not play. |
Attach |
Once, at the first update. |
Loop |
In a loop from the first update. |
WhileTrue |
In a loop while the bool value is true. |
Built-in presets (WindowAnimations.PRESETS):
| Preset | Look |
|---|---|
fade-in |
From opacity 0. |
fade-out |
To opacity 0. Use with keep. |
slide-up, slide-down, slide-left, slide-right |
Fades in and moves in from 24 px below, 24 px above, 40 px right or 40 px left. |
pop |
From opacity 0 and scale 0.6, with an overshoot. |
shake |
Four fast side moves. |
pulse |
Scale 1.08 and back. |
flash |
Text in --tb-color-warning and scale 1.15, and back. |
blink |
Opacity 0.25 and back. |
bounce |
Up 12 px, down, up 5 px, down. |
A preset is USS only. To add one, write these classes in any sheet. PlayPresetAsync adds and removes them:
| Class | Holds |
|---|---|
.tb-anim-{name} |
The transition-* properties, and --tb-anim-steps: n (default 0). It is on the element while the preset plays. |
.tb-anim-{name}--from |
Optional. The start look, with transition-duration: 0s so that it applies at once. |
.tb-anim-{name}--1 to --n |
Optional. The steps. Each step transitions from the last. |
At the end the element transitions back to its own look. Example: a glow that grows in two steps.
.tb-anim-glow {
--tb-anim-steps: 2;
transition-property: border-color, scale;
transition-duration: 120ms;
}
.tb-anim-glow--1 { border-color: #ffd54a; scale: 1.1 1.1; }
.tb-anim-glow--2 { border-color: #fff3b0; scale: 1.2 1.2; }
Rules:
- A new play of the same preset on an element stops the old one. Different presets can play at the same time, if they animate different properties.
- The play ends at the end, on
StopPreset, when the token is cancelled, or when the element leaves its panel. Nothing plays when the element is not in a panel or whenAnimationsEnabledis false. - The preset classes load on the panel root. A rule of the element with the same specificity in a sheet nearer the element wins. If an element rule sets the same property, such as
opacity, make the preset selector more specific, for example.my-card.tb-anim-glow--1. - A step waits for the longest
transition-durationplustransition-delay, not for the end events.
Per-step timing
A step rule can set its own transition-duration, transition-delay and transition-timing-function. They apply to the transition into that step. The step rule must come after the base rule, because at equal specificity the later rule wins. The return to the element's own look uses the timing of the base rule.
.tb-anim-coin { --tb-anim-steps: 2; transition-property: translate; transition-duration: 220ms; }
.tb-anim-coin--1 { translate: 0 -8px; transition-duration: 70ms; }
.tb-anim-coin--2 { translate: 0 0; transition-duration: 260ms; transition-timing-function: ease-out-bounce; }
Animation Editor
Tools > UI Toolbox > Animation Editor edits presets with a live preview, in edit mode.
- The list shows each preset in the USS files of the project, with its file.
- The preview plays the preset on a sample element (label, button, card, meter or window). Play, Loop, Step (one step at a time) and Stop.
- The editor sets the name, the base timing, the steps with their properties and their own timing, and a scope selector. A curve shows the easing. "One play" shows the total time.
- A property row has a helper for its type: a slider for
opacity, two fields forscaleandtranslate, a degree field forrotateand a color field for colors. Any other USS property takes text. - Save writes the preset to the sheet in "Save To". The "Generated USS" foldout shows the text.
The editor writes a preset between two markers. It replaces only that block on the next save:
/* tb-anim-editor: begin coin */
...
/* tb-anim-editor: end coin */
The editor opens a preset outside the markers read-only, for example the built-in presets and presets written by hand. Duplicate makes an editable copy. The editor does not save to WindowAnimations.uss.
The scope is the selector text before the preset classes, for example .kingdom .tb-toolbar Label. It makes the step rules more specific than the element rules. The base class .tb-anim-{name} is not scoped.
The preset attribute of AnimateBinding has a picker in the Inspector and in UI Builder. It lists the known presets and warns about a name that no sheet defines. Put [PresetName] on your own string field or property to get the same picker.