Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Players

The Player trait is the contract for time-based animation sources (Lottie, Rive, custom scene files). Implement it once; the same sketch can then drive any player without knowing the format.

The trait

#![allow(unused)]
fn main() {
pub trait Player: Send + 'static {
    fn duration(&self) -> Option<f32>;
    fn draw_at(&mut self, ctx: &mut SketchContext<'_>, rect: Rect, t: f32);
    fn seek(&mut self, _t: f32) {}
    fn set_playing(&mut self, _playing: bool) {}
}
}
MethodDefaultPurpose
duration()requiredTotal playback duration in seconds. None signals content that plays indefinitely (procedural, live, user-controlled).
draw_at(ctx, rect, t)requiredRender one frame at time t into rect. Interpolate the scene at t, dispatch draw calls into ctx.
seek(t)no-opSeek internal playback to t. Players that derive every frame from the incoming t don’t need to override.
set_playing(playing)no-opPause / resume. Paused players should render their frozen pose and ignore t in draw_at.

Playing a Lottie scene

blinc_lottie::LottiePlayer implements Player. Wrap it in a sketch to run at any size:

#![allow(unused)]
fn main() {
use blinc_app::prelude::*;
use blinc_canvas_kit::prelude::*;
use blinc_core::{Color, Rect};
use blinc_lottie::LottiePlayer;

const LOTTIE_JSON: &str = include_str!("assets/my_animation.json");

struct Loader {
    player: LottiePlayer,
}

impl Sketch for Loader {
    fn draw(&mut self, ctx: &mut SketchContext<'_>, t: f32, _dt: f32) {
        let size = ctx.width.min(ctx.height);
        let x = (ctx.width - size) * 0.5;
        let y = (ctx.height - size) * 0.5;
        ctx.play(&mut self.player, Rect::new(x, y, size, size), t);
    }
}

fn build_ui() -> impl ElementBuilder {
    let player = LottiePlayer::from_json(LOTTIE_JSON).expect("parse Lottie");
    div()
        .w_full()
        .h_full()
        .bg(Color::WHITE)
        .child(sketch("lottie", Loader { player }))
}
}

ctx.play(&mut player, rect, t) is a thin forwarder over Player::draw_at — provided so sketches holding a player on self don’t hit borrow-checker friction when draw also reads other self fields.

Lottie specifically supports both plain JSON (from_json) and .lottie archives (from_dotlottie_bytes, requires the dotlottie feature). See the blinc_lottie crate for asset-loading variants.

Writing your own player

Anything that can resolve a pose from a float time implements Player. A minimal example: a player that renders an orbiting dot.

#![allow(unused)]
fn main() {
use blinc_canvas_kit::prelude::*;
use blinc_core::{Color, CornerRadius, Brush, Rect};

struct Orbit;

impl Player for Orbit {
    fn duration(&self) -> Option<f32> { None }  // plays forever

    fn draw_at(&mut self, ctx: &mut SketchContext<'_>, rect: Rect, t: f32) {
        let cx = rect.x() + rect.width() * 0.5;
        let cy = rect.y() + rect.height() * 0.5;
        let r = rect.width().min(rect.height()) * 0.4;
        let a = t * std::f32::consts::TAU * 0.5;
        let x = cx + r * a.cos() - 8.0;
        let y = cy + r * a.sin() - 8.0;

        ctx.draw_context().fill_rect(
            Rect::new(x, y, 16.0, 16.0),
            CornerRadius::uniform(8.0),
            Brush::Solid(Color::WHITE),
        );
    }
}
}

Drop Orbit into any sketch via ctx.play(&mut orbit, rect, t) and it composes with other players, sketches, and UI in the same frame.