Transitions
Transitions occur when you have a conditional view like if_view! or match_view! which
changes branches. Because the branches contain different subtrees, there is no
reasonable way to animate between them. In cases like this, the conditional
view uses a transition to animate between the two branches.
The properties of views within unchanged branches are still animated as normal.
Configuring Transitions
The Opacity transition is used by default, but it can be changed with the .transition()
modifier.
#![allow(unused)]
fn main() {
extern crate buoyant;
extern crate embedded_graphics;
use embedded_graphics::pixelcolor::Rgb888;
use std::time::Duration;
use buoyant::{
if_view,
view::prelude::*,
transition::{Move, Slide},
};
fn maybe_round(is_square: bool) -> impl View<Rgb888, ()> {
if_view!((is_square) {
Rectangle
.frame_sized(20, 20)
.transition(Slide::leading())
} else {
Circle
.frame_sized(20, 20)
.transition(Move::top())
})
.animated(Animation::ease_out(Duration::from_millis(120)), is_square)
}
}
Transitions must have some parent animation node with a value that matches the condition
for the transition or they will not animate. The transition duration is determined by the
animation node driving it.
For transitions like Move and Slide, the size of the whole transitioning subtree is used
to determine how far to move the view. The transition modifier does not need to be the
outermost modifier, and where you place it has no bearing on the resulting effect.
These two views produce the exact same transition:
#![allow(unused)]
fn main() {
extern crate buoyant;
extern crate embedded_graphics;
use embedded_graphics::pixelcolor::Rgb888;
use std::time::Duration;
use buoyant::{
if_view,
view::prelude::*,
transition::{Move, Slide},
};
fn all_the_same_1(is_square: bool) -> impl View<Rgb888, ()> {
if_view!((is_square) {
Rectangle
.frame_sized(20, 20)
.padding(Edges::All, 5)
.transition(Slide::leading()) // Outermost modifier
})
.animated(Animation::ease_out(Duration::from_millis(120)), is_square)
}
fn all_the_same_2(is_square: bool) -> impl View<Rgb888, ()> {
if_view!((is_square) {
Rectangle
.transition(Slide::leading()) // Innermost modifier
.frame_sized(20, 20)
.padding(Edges::All, 5)
})
.animated(Animation::ease_out(Duration::from_millis(120)), is_square)
}
}
Where To Apply Transitions
For views like VStack where there is no obvious way to choose between the
transitions requested by its children, the default Opacity will be used.
The transitions applied inside the stack here have no effect:
#![allow(unused)]
fn main() {
extern crate buoyant;
extern crate embedded_graphics;
use embedded_graphics::pixelcolor::Rgb888;
use std::time::Duration;
use buoyant::{
if_view,
view::prelude::*,
transition::{Move, Slide},
};
fn lost_in_the_stacks(thing: bool) -> impl View<Rgb888, ()> {
if_view!((thing) {
VStack::new((
Rectangle.transition(Slide::trailing()),
Rectangle.transition(Move::leading())
)) // .transition(...) <-- Here would work!
})
.animated(Animation::ease_out(Duration::from_millis(120)), thing)
}
}
To get the stack in this example to transition, apply the transition outside the stack.
Prefer Computed Properties
Unless transitioning is the desired behavior, prefer using computed properties over conditional views when the conditional view’s branches are the same type.
#![allow(unused)]
fn main() {
extern crate buoyant;
extern crate embedded_graphics;
use buoyant::{
if_view,
view::prelude::*,
};
use embedded_graphics::pixelcolor::Rgb888;
/// This will jump between two different rectangles
fn bar1(is_wide: bool) -> impl View<Rgb888, ()> {
if_view!((is_wide) {
Rectangle.frame_sized(100, 5)
} else {
Rectangle.frame_sized(20, 5)
})
}
/// This will animate the frame of the Rectangle
fn bar2(is_wide: bool) -> impl View<Rgb888, ()> {
Rectangle.frame_sized(if is_wide { 100 } else { 20 }, 5)
}
}