lv_bevy_ecs 0.9.4

Safe Rust bindings to the LVGL graphics library using Bevy's ECS framework
Documentation
//! # Animations
//!
//! Animations are components that need to be added to entities
//!
//! ```
//! # use core::time::Duration;
//! # use lv_bevy_ecs::animation::Animation;
//! # use lv_bevy_ecs::functions::*;
//! # use lv_bevy_ecs::support::OpacityLevel;
//! # use lv_bevy_ecs::sys::{lv_part_t_LV_PART_MAIN, lv_anim_count_running};
//! # use lv_bevy_ecs::widgets::{Button, LvglWorld};
//! #
//! # lv_bevy_ecs::setup_test_display!();
//! #
//! let mut world = LvglWorld::default();
//! let button = Button::create_widget();
//!
//! let anim = Animation::new(
//!     Duration::from_secs(5),
//!     OpacityLevel::Transparent as i32,
//!     OpacityLevel::Cover as i32,
//!     |obj, val| {
//!         lv_obj_set_style_opa(obj, val as u8, lv_part_t_LV_PART_MAIN);
//!     },
//! );
//! let mut button_entity = world.spawn((Button, button, anim));
//! unsafe {
//!     assert_eq!(lv_anim_count_running(), 1);
//! }
//! ```

use ::alloc::boxed::Box;
use ::core::{ffi::c_void, mem::MaybeUninit, ptr::NonNull, time::Duration};

use bevy_ecs::{component::Component, lifecycle::HookContext, world::DeferredWorld};

use crate::widgets::{Wdg, Widget};

#[derive(Component)]
#[component(on_insert = add_animation)]
#[component(storage = "SparseSet")]
pub struct Animation {
    raw: lightvgl_sys::lv_anim_t,
}

impl Animation {
    pub fn new<F>(duration: Duration, start: i32, end: i32, animator: F) -> Self
    where
        F: FnMut(&mut Wdg, i32),
    {
        unsafe {
            let mut anim = MaybeUninit::<lightvgl_sys::lv_anim_t>::uninit();
            lightvgl_sys::lv_anim_init(anim.as_mut_ptr());
            let mut raw = anim.assume_init();

            lightvgl_sys::lv_anim_set_duration(
                &mut raw,
                duration.as_millis().try_into().unwrap_or(0),
            );
            lightvgl_sys::lv_anim_set_values(&mut raw, start, end);
            lightvgl_sys::lv_anim_set_user_data(
                &mut raw,
                Box::<F>::into_raw(Box::new(animator)).cast(),
            );
            lightvgl_sys::lv_anim_set_exec_cb(&mut raw, Some(animator_trampoline::<F>));

            Self { raw }
        }
    }

    #[cfg(feature = "no_ecs")]
    pub fn set_widget(&mut self, widget: &mut Wdg) {
        unsafe {
            lightvgl_sys::lv_anim_set_var(self.raw_mut(), widget.raw_mut().cast());
        }
    }

    pub fn start(&mut self) {
        unsafe {
            let old_ptr = &mut self.raw;
            let new_ptr = lightvgl_sys::lv_anim_start(old_ptr);
            self.raw = *new_ptr;
        }
    }

    pub fn raw(&self) -> &lightvgl_sys::lv_anim_t {
        &self.raw
    }

    pub fn raw_mut(&mut self) -> &mut lightvgl_sys::lv_anim_t {
        &mut self.raw
    }
}

unsafe impl Send for Animation {}
unsafe impl Sync for Animation {}

impl Drop for Animation {
    fn drop(&mut self) {
        crate::info!("Dropping Animation");
    }
}

fn add_animation(mut world: DeferredWorld, ctx: HookContext) {
    let obj = world
        .get_mut::<Widget>(ctx.entity)
        .expect("Animation components must be added entities with a Widget component")
        .as_mut()
        .raw_mut();
    let mut anim = world.get_mut::<Animation>(ctx.entity).unwrap();
    unsafe {
        lightvgl_sys::lv_anim_set_var(anim.raw_mut(), obj.cast());
    }

    anim.start();
    crate::info!("Added Animation");
}

unsafe extern "C" fn animator_trampoline<F>(obj: *mut c_void, val: i32)
where
    F: FnMut(&mut Wdg, i32),
{
    unsafe {
        let ptr = lightvgl_sys::lv_anim_get(obj, None);
        let anim = NonNull::new(ptr).unwrap();
        let obj = obj.cast();
        let user_data = lightvgl_sys::lv_anim_get_user_data(anim.as_ref());
        if user_data.is_null() {
            crate::warn!("Animation user data was null, this should never happen!");
            return;
        }
        let mut obj_wdg = Wdg::from_ptr(obj);
        let callback = &mut *(user_data.cast::<F>());
        callback(&mut obj_wdg, val);
    }
}