ohos-arkui-binding 0.2.0

OpenHarmony's arkui binding for rust
Documentation
//! Module animate::options::keyframe_animate_option wrappers and related types.

use std::{cell::RefCell, collections::HashMap, os::raw::c_void, ptr::NonNull, rc::Rc};

use ohos_arkui_input_binding::ArkUIErrorCode;
use ohos_arkui_sys::{
    ArkUI_KeyframeAnimateOption, OH_ArkUI_KeyframeAnimateOption_Create,
    OH_ArkUI_KeyframeAnimateOption_Dispose, OH_ArkUI_KeyframeAnimateOption_GetCurve,
    OH_ArkUI_KeyframeAnimateOption_GetDelay, OH_ArkUI_KeyframeAnimateOption_GetDuration,
    OH_ArkUI_KeyframeAnimateOption_GetIterations,
    OH_ArkUI_KeyframeAnimateOption_RegisterOnEventCallback,
    OH_ArkUI_KeyframeAnimateOption_RegisterOnFinishCallback,
    OH_ArkUI_KeyframeAnimateOption_SetCurve, OH_ArkUI_KeyframeAnimateOption_SetDelay,
    OH_ArkUI_KeyframeAnimateOption_SetDuration, OH_ArkUI_KeyframeAnimateOption_SetIterations,
};

#[cfg(feature = "api-19")]
use ohos_arkui_sys::{
    OH_ArkUI_KeyframeAnimateOption_GetExpectedFrameRate,
    OH_ArkUI_KeyframeAnimateOption_SetExpectedFrameRate,
};

use crate::animate::curve::CurveHandle;
use crate::api::ARK_UI_NATIVE_ANIMATE_API_1;
use crate::{check_arkui_status, ArkUIContext, ArkUIError, ArkUIResult};

#[cfg(feature = "api-19")]
use super::AnimationFrameRateRange;

/// High-level wrapper for `ArkUI_KeyframeAnimateOption`.
pub struct KeyframeAnimation {
    raw: Rc<RefCell<NonNull<ArkUI_KeyframeAnimateOption>>>,
    finish_callback: RefCell<Option<*mut KeyframeCallbackContext>>,
    event_callbacks: RefCell<HashMap<i32, *mut KeyframeCallbackContext>>,
}

struct KeyframeCallbackContext {
    callback: Box<dyn Fn()>,
}

impl KeyframeAnimation {
    /// Creates keyframe animation options with the given keyframe count.
    pub fn new(size: i32) -> ArkUIResult<Self> {
        let option = unsafe { OH_ArkUI_KeyframeAnimateOption_Create(size) };
        let option = NonNull::new(option).ok_or_else(|| {
            ArkUIError::new(
                ArkUIErrorCode::ParamInvalid,
                "OH_ArkUI_KeyframeAnimateOption_Create returned null",
            )
        })?;

        Ok(Self {
            raw: Rc::new(RefCell::new(option)),
            finish_callback: RefCell::new(None),
            event_callbacks: RefCell::new(HashMap::new()),
        })
    }

    pub(crate) fn raw(&self) -> *mut ArkUI_KeyframeAnimateOption {
        self.raw.borrow().as_ptr()
    }

    pub fn delay(&self, delay: i32) -> ArkUIResult<()> {
        check_arkui_status!(unsafe { OH_ArkUI_KeyframeAnimateOption_SetDelay(self.raw(), delay) })
    }

    pub fn get_delay(&self) -> i32 {
        unsafe { OH_ArkUI_KeyframeAnimateOption_GetDelay(self.raw()) }
    }

    pub fn iterations(&self, iterations: i32) -> ArkUIResult<()> {
        check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_SetIterations(self.raw(), iterations)
        })
    }

    pub fn get_iterations(&self) -> i32 {
        unsafe { OH_ArkUI_KeyframeAnimateOption_GetIterations(self.raw()) }
    }

    /// Registers finish callback for keyframe animation.
    pub fn on_finish_callback<T: Fn() + 'static>(&self, on_finish: T) -> ArkUIResult<()> {
        let callback = Box::into_raw(Box::new(KeyframeCallbackContext {
            callback: Box::new(on_finish),
        }));
        let result = check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_RegisterOnFinishCallback(
                self.raw(),
                callback.cast(),
                Some(keyframe_callback_trampoline),
            )
        });

        if let Err(err) = result {
            unsafe {
                drop(Box::from_raw(callback));
            }
            return Err(err);
        }

        if let Some(old) = self.finish_callback.borrow_mut().replace(callback) {
            unsafe {
                drop(Box::from_raw(old));
            }
        }

        Ok(())
    }

    /// Clears finish callback.
    pub fn clear_on_finish_callback(&self) -> ArkUIResult<()> {
        check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_RegisterOnFinishCallback(
                self.raw(),
                std::ptr::null_mut(),
                None,
            )
        })?;

        if let Some(old) = self.finish_callback.borrow_mut().take() {
            unsafe {
                drop(Box::from_raw(old));
            }
        }

        Ok(())
    }

    /// Registers event callback for keyframe at `index`.
    pub fn on_event_callback<T: Fn() + 'static>(&self, index: i32, event: T) -> ArkUIResult<()> {
        let callback = Box::into_raw(Box::new(KeyframeCallbackContext {
            callback: Box::new(event),
        }));
        let result = check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_RegisterOnEventCallback(
                self.raw(),
                callback.cast(),
                Some(keyframe_callback_trampoline),
                index,
            )
        });

        if let Err(err) = result {
            unsafe {
                drop(Box::from_raw(callback));
            }
            return Err(err);
        }

        if let Some(old) = self.event_callbacks.borrow_mut().insert(index, callback) {
            unsafe {
                drop(Box::from_raw(old));
            }
        }

        Ok(())
    }

    /// Clears event callback for keyframe at `index`.
    pub fn clear_on_event_callback(&self, index: i32) -> ArkUIResult<()> {
        check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_RegisterOnEventCallback(
                self.raw(),
                std::ptr::null_mut(),
                None,
                index,
            )
        })?;

        if let Some(old) = self.event_callbacks.borrow_mut().remove(&index) {
            unsafe {
                drop(Box::from_raw(old));
            }
        }

        Ok(())
    }

    /// Sets keyframe duration for the specified index.
    pub fn duration(&self, duration: i32, index: i32) -> ArkUIResult<()> {
        check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_SetDuration(self.raw(), duration, index)
        })
    }

    pub fn get_duration(&self, index: i32) -> i32 {
        unsafe { OH_ArkUI_KeyframeAnimateOption_GetDuration(self.raw(), index) }
    }

    /// Sets keyframe curve for the specified index.
    pub fn curve(&self, curve: &CurveHandle, index: i32) -> ArkUIResult<()> {
        check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_SetCurve(self.raw(), curve.as_raw(), index)
        })
    }

    pub fn get_curve(&self, index: i32) -> Option<CurveHandle> {
        let curve = unsafe { OH_ArkUI_KeyframeAnimateOption_GetCurve(self.raw(), index) };
        CurveHandle::from_raw_borrowed(curve)
    }

    #[cfg(feature = "api-19")]
    pub fn rate_range(&self, range: AnimationFrameRateRange) -> ArkUIResult<()> {
        let mut raw_range = range.raw();
        check_arkui_status!(unsafe {
            OH_ArkUI_KeyframeAnimateOption_SetExpectedFrameRate(self.raw(), &mut raw_range)
        })
    }

    #[cfg(feature = "api-19")]
    pub fn get_rate_range(&self) -> AnimationFrameRateRange {
        let range_ptr = unsafe { OH_ArkUI_KeyframeAnimateOption_GetExpectedFrameRate(self.raw()) };
        if range_ptr.is_null() {
            return AnimationFrameRateRange::new();
        }

        let range = unsafe { *range_ptr };
        AnimationFrameRateRange::from_raw(range)
    }

    #[cfg(feature = "napi")]
    pub fn animate_to(&self, ctx: ArkUIContext) -> ArkUIResult<()> {
        ARK_UI_NATIVE_ANIMATE_API_1.with(|api| api.keyframe_animate_to(ctx.raw(), self.raw()))?;
        Ok(())
    }
}

impl Drop for KeyframeAnimation {
    fn drop(&mut self) {
        if let Some(callback) = self.finish_callback.borrow_mut().take() {
            unsafe {
                drop(Box::from_raw(callback));
            }
        }

        for (_, callback) in self.event_callbacks.borrow_mut().drain() {
            unsafe {
                drop(Box::from_raw(callback));
            }
        }

        unsafe { OH_ArkUI_KeyframeAnimateOption_Dispose(self.raw()) };
    }
}

unsafe extern "C" fn keyframe_callback_trampoline(data: *mut c_void) {
    if data.is_null() {
        return;
    }

    let callback = unsafe { &*(data as *mut KeyframeCallbackContext) };
    (callback.callback)();
}