diff --git a/Makefile b/Makefile index 29001634..50976d6a 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ WASM_BUILD := $(BUILD)/$(WASM) EMSDK := emsdk EMSDK_DIR := $(PROJECT_DIR)/$(DEPS_MODULES_DIR)/$(EMSDK) -EMSDK_VERSION := 3.1.68 +EMSDK_VERSION := 3.1.71 EMSDK_ENV := emsdk_env.sh UNIFFI_BINDGEN_CPP := uniffi-bindgen-cpp diff --git a/deps/modules/emsdk b/deps/modules/emsdk index 54ef0883..59108b0e 160000 --- a/deps/modules/emsdk +++ b/deps/modules/emsdk @@ -1 +1 @@ -Subproject commit 54ef088329e5a329614b3659a579d2ccd31fd621 +Subproject commit 59108b0e222d4c38530a5f2bebcecc7db965f52d diff --git a/dotlottie-ffi/bindings.h b/dotlottie-ffi/bindings.h index 4ba417eb..be01abd3 100644 --- a/dotlottie-ffi/bindings.h +++ b/dotlottie-ffi/bindings.h @@ -11,8 +11,6 @@ #define DOTLOTTIE_MANIFEST_NOT_AVAILABLE 3 -#define DOTLOTTIE_MANIFEST_THEMES_NOT_AVAILABLE 4 - #define DOTLOTTIE_MAX_STR_LENGTH 512 #define DOTLOTTIE_SUCCESS 0 @@ -68,6 +66,7 @@ typedef struct DotLottieConfig { uint32_t background_color; struct DotLottieLayout layout; struct DotLottieString marker; + struct DotLottieString theme_id; } DotLottieConfig; typedef struct LayerBoundingBox { @@ -82,62 +81,28 @@ typedef struct DotLottieOption_DotLottieString { bool defined; } DotLottieOption_DotLottieString; -typedef struct DotLottieOption_u32 { - uint32_t value; - bool defined; -} DotLottieOption_u32; - typedef struct DotLottieManifest { - struct DotLottieOption_DotLottieString active_animation_id; - struct DotLottieOption_DotLottieString author; - struct DotLottieOption_DotLottieString description; struct DotLottieOption_DotLottieString generator; - struct DotLottieOption_DotLottieString keywords; - struct DotLottieOption_u32 revision; struct DotLottieOption_DotLottieString version; } DotLottieManifest; -typedef struct DotLottieOption_bool { - bool value; - bool defined; -} DotLottieOption_bool; - -typedef struct DotLottieOption_i8 { - int8_t value; - bool defined; -} DotLottieOption_i8; - -typedef struct DotLottieOption_f32 { - float value; - bool defined; -} DotLottieOption_f32; - typedef struct DotLottieManifestAnimation { - struct DotLottieOption_bool autoplay; - struct DotLottieOption_DotLottieString default_theme; - struct DotLottieOption_i8 direction; - struct DotLottieOption_bool hover; struct DotLottieOption_DotLottieString id; - struct DotLottieOption_u32 intermission; - struct DotLottieOption_bool loop; - struct DotLottieOption_u32 loop_count; - struct DotLottieOption_DotLottieString play_mode; - struct DotLottieOption_f32 speed; - struct DotLottieOption_DotLottieString theme_color; + struct DotLottieOption_DotLottieString name; + struct DotLottieOption_DotLottieString initial_theme; + struct DotLottieOption_DotLottieString background; } DotLottieManifestAnimation; -typedef struct DotLottieManifestState { - struct DotLottieString state; -} DotLottieManifestState; +typedef struct DotLottieManifestStateMachine { + struct DotLottieString id; + struct DotLottieOption_DotLottieString name; +} DotLottieManifestStateMachine; typedef struct DotLottieManifestTheme { struct DotLottieString id; + struct DotLottieOption_DotLottieString name; } DotLottieManifestTheme; -typedef struct DotLottieManifestThemeAnimation { - struct DotLottieString id; -} DotLottieManifestThemeAnimation; - typedef struct DotLottieMarker { struct DotLottieString name; float duration; @@ -308,10 +273,6 @@ int32_t dotlottie_load_state_machine(struct DotLottiePlayer *ptr, const char *st int32_t dotlottie_load_state_machine_data(struct DotLottiePlayer *ptr, const char *state_machine_definition); -int32_t dotlottie_load_theme(struct DotLottiePlayer *ptr, const char *theme_id); - -int32_t dotlottie_load_theme_data(struct DotLottiePlayer *ptr, const char *theme_data); - int32_t dotlottie_loop_count(struct DotLottiePlayer *ptr, uint32_t *result); int32_t dotlottie_manifest(struct DotLottiePlayer *ptr, struct DotLottieManifest *result); @@ -320,14 +281,9 @@ int32_t dotlottie_manifest_animations(struct DotLottiePlayer *ptr, struct DotLottieManifestAnimation *result, size_t *size); -int32_t dotlottie_manifest_states(struct DotLottiePlayer *ptr, - struct DotLottieManifestState *result, - size_t *size); - -int32_t dotlottie_manifest_theme_animations(struct DotLottiePlayer *ptr, - const struct DotLottieManifestTheme *theme, - struct DotLottieManifestThemeAnimation *result, - size_t *size); +int32_t dotlottie_manifest_state_machines(struct DotLottiePlayer *ptr, + struct DotLottieManifestStateMachine *result, + size_t *size); int32_t dotlottie_manifest_themes(struct DotLottiePlayer *ptr, struct DotLottieManifestTheme *result, @@ -349,6 +305,8 @@ int32_t dotlottie_render(struct DotLottiePlayer *ptr); int32_t dotlottie_request_frame(struct DotLottiePlayer *ptr, float *result); +int32_t dotlottie_reset_theme(struct DotLottiePlayer *ptr); + int32_t dotlottie_resize(struct DotLottiePlayer *ptr, uint32_t width, uint32_t height); int32_t dotlottie_seek(struct DotLottiePlayer *ptr, float no); @@ -369,6 +327,10 @@ int32_t dotlottie_set_state_machine_string_context(struct DotLottiePlayer *ptr, const char *key, const char *value); +int32_t dotlottie_set_theme(struct DotLottiePlayer *ptr, const char *theme_id); + +int32_t dotlottie_set_theme_data(struct DotLottiePlayer *ptr, const char *theme_data); + int32_t dotlottie_set_viewport(struct DotLottiePlayer *ptr, int32_t x, int32_t y, diff --git a/dotlottie-ffi/emscripten_bindings.cpp b/dotlottie-ffi/emscripten_bindings.cpp index b31e7c27..1e8b8967 100644 --- a/dotlottie-ffi/emscripten_bindings.cpp +++ b/dotlottie-ffi/emscripten_bindings.cpp @@ -75,9 +75,11 @@ EMSCRIPTEN_BINDINGS(DotLottiePlayer) .field("segment", &Config::segment) .field("backgroundColor", &Config::background_color) .field("layout", &Config::layout) - .field("marker", &Config::marker); + .field("marker", &Config::marker) + .field("themeId", &Config::theme_id); function("createDefaultConfig", &create_default_config); + function("transformThemeToLottieSlots", &transform_theme_to_lottie_slots); // value_object("ManifestTheme") // .field("id", &ManifestTheme::id) @@ -157,8 +159,10 @@ EMSCRIPTEN_BINDINGS(DotLottiePlayer) // .function("subscribe", &DotLottiePlayer::subscribe) // .function("unsubscribe", &DotLottiePlayer::unsubscribe) .function("isComplete", &DotLottiePlayer::is_complete) - .function("loadTheme", &DotLottiePlayer::load_theme) - .function("loadThemeData", &DotLottiePlayer::load_theme_data) + .function("setTheme", &DotLottiePlayer::set_theme) + .function("setThemeData", &DotLottiePlayer::set_theme_data) + .function("resetTheme", &DotLottiePlayer::reset_theme) + .function("setSlots", &DotLottiePlayer::set_slots) .function("markers", &DotLottiePlayer::markers) .function("activeAnimationId", &DotLottiePlayer::active_animation_id) .function("activeThemeId", &DotLottiePlayer::active_theme_id) diff --git a/dotlottie-ffi/src/dotlottie_player.udl b/dotlottie-ffi/src/dotlottie_player.udl index ea62848d..1dc562a4 100644 --- a/dotlottie-ffi/src/dotlottie_player.udl +++ b/dotlottie-ffi/src/dotlottie_player.udl @@ -54,38 +54,39 @@ dictionary Config { u32 background_color; Layout layout; string marker; + string theme_id; +}; + +dictionary ManifestInitial { + string? animation; + string? state_machine; +}; + +dictionary ManifestAnimation { + string id; + string? name; + string? initial_theme; + sequence? themes; + string? background; }; dictionary ManifestTheme { string id; - sequence animations; + string? name; }; -dictionary ManifestAnimation { - boolean? autoplay; - string? defaultTheme; - i8? direction; - boolean? hover; +dictionary ManifestStateMachine { string id; - u32? intermission; - boolean? loop; - u32? loop_count; - string? playMode; - f32? speed; - string? themeColor; + string? name; }; dictionary Manifest { - string? active_animation_id; - sequence animations; - string? author; - string? description; + string? version; string? generator; - string? keywords; - u32? revision; + ManifestInitial? initial; + sequence animations; sequence? themes; - sequence? states; - string? version; + sequence? state_machines; }; dictionary Marker { @@ -140,8 +141,10 @@ interface DotLottiePlayer { void subscribe(Observer observer); void unsubscribe([ByRef] Observer observer); boolean is_complete(); - boolean load_theme([ByRef] string theme_id); - boolean load_theme_data([ByRef] string theme_data); + boolean set_theme([ByRef] string theme_id); + boolean set_theme_data([ByRef] string theme_data); + boolean reset_theme(); + boolean set_slots([ByRef] string slots); sequence markers(); string active_animation_id(); string active_theme_id(); diff --git a/dotlottie-ffi/src/dotlottie_player_cpp.udl b/dotlottie-ffi/src/dotlottie_player_cpp.udl index 7fa2b792..cabc1ab7 100644 --- a/dotlottie-ffi/src/dotlottie_player_cpp.udl +++ b/dotlottie-ffi/src/dotlottie_player_cpp.udl @@ -1,6 +1,7 @@ namespace dotlottie_player { Layout create_default_layout(); Config create_default_config(); + string transform_theme_to_lottie_slots([ByRef] string theme_data, [ByRef] string animation_id); }; enum Mode { @@ -34,6 +35,7 @@ dictionary Config { u32 background_color; Layout layout; string marker; + string theme_id; }; dictionary Marker { @@ -71,8 +73,10 @@ interface DotLottiePlayer { boolean resize(u32 width, u32 height); void clear(); boolean is_complete(); - boolean load_theme([ByRef] string theme_id); - boolean load_theme_data([ByRef] string theme_data); + boolean set_theme([ByRef] string theme_id); + boolean set_theme_data([ByRef] string theme_data); + boolean reset_theme(); + boolean set_slots([ByRef] string slots); sequence markers(); string active_animation_id(); string active_theme_id(); diff --git a/dotlottie-ffi/src/ffi/mod.rs b/dotlottie-ffi/src/ffi/mod.rs index 11999d23..b60061c5 100644 --- a/dotlottie-ffi/src/ffi/mod.rs +++ b/dotlottie-ffi/src/ffi/mod.rs @@ -5,6 +5,9 @@ use types::*; pub mod types; +// TODO: dotlottie_manifest_initial +// TODO: dotlottie_manifest_animation_themes + // Allows to wrap every C API call with some additional logic. This is currently used to // check if the dotlottie player pointer is valid or not unsafe fn exec_dotlottie_player_op(ptr: *mut DotLottiePlayer, op: Op) -> i32 @@ -160,8 +163,8 @@ pub unsafe extern "C" fn dotlottie_manifest_themes( Some(v) => v, None => return DOTLOTTIE_MANIFEST_NOT_AVAILABLE, }; - if let Some(themes) = &manifest.themes { - DotLottieManifestTheme::transfer_all(themes, result, size) + if let Some(themes) = manifest.themes { + DotLottieManifestTheme::transfer_all(&themes, result, size) } else { *size = 0; DOTLOTTIE_SUCCESS @@ -170,41 +173,9 @@ pub unsafe extern "C" fn dotlottie_manifest_themes( } #[no_mangle] -pub unsafe extern "C" fn dotlottie_manifest_theme_animations( - ptr: *mut DotLottiePlayer, - theme: *const types::DotLottieManifestTheme, - result: *mut types::DotLottieManifestThemeAnimation, - size: *mut usize, -) -> i32 { - exec_dotlottie_player_op(ptr, |dotlottie_player| { - if theme.is_null() { - return DOTLOTTIE_INVALID_PARAMETER; - } - let theme = match theme.as_ref() { - Some(v) => v, - None => return DOTLOTTIE_INVALID_PARAMETER, - }; - let theme_id = theme.id.to_string(); - let manifest = match dotlottie_player.manifest() { - Some(v) => v, - None => return DOTLOTTIE_MANIFEST_NOT_AVAILABLE, - }; - let themes = match manifest.themes { - Some(v) => v, - None => return DOTLOTTIE_MANIFEST_THEMES_NOT_AVAILABLE, - }; - if let Some(theme) = themes.iter().find(|&v| v.id == theme_id) { - DotLottieManifestThemeAnimation::transfer_all(&theme.animations, result, size) - } else { - DOTLOTTIE_INVALID_PARAMETER - } - }) -} - -#[no_mangle] -pub unsafe extern "C" fn dotlottie_manifest_states( +pub unsafe extern "C" fn dotlottie_manifest_state_machines( ptr: *mut DotLottiePlayer, - result: *mut types::DotLottieManifestState, + result: *mut types::DotLottieManifestStateMachine, size: *mut usize, ) -> i32 { exec_dotlottie_player_op(ptr, |dotlottie_player| { @@ -212,8 +183,8 @@ pub unsafe extern "C" fn dotlottie_manifest_states( Some(v) => v, None => return DOTLOTTIE_MANIFEST_NOT_AVAILABLE, }; - if let Some(states) = manifest.states { - DotLottieManifestState::transfer_all(&states, result, size) + if let Some(state_machines) = manifest.state_machines { + DotLottieManifestStateMachine::transfer_all(&state_machines, result, size) } else { *size = 0; DOTLOTTIE_SUCCESS @@ -446,13 +417,13 @@ pub unsafe extern "C" fn dotlottie_is_complete( } #[no_mangle] -pub unsafe extern "C" fn dotlottie_load_theme( +pub unsafe extern "C" fn dotlottie_set_theme( ptr: *mut DotLottiePlayer, theme_id: *const c_char, ) -> i32 { exec_dotlottie_player_op(ptr, |dotlottie_player| { if let Ok(theme_id) = DotLottieString::read(theme_id) { - to_exit_status(dotlottie_player.load_theme(&theme_id)) + to_exit_status(dotlottie_player.set_theme(&theme_id)) } else { DOTLOTTIE_INVALID_PARAMETER } @@ -460,13 +431,20 @@ pub unsafe extern "C" fn dotlottie_load_theme( } #[no_mangle] -pub unsafe extern "C" fn dotlottie_load_theme_data( +pub unsafe extern "C" fn dotlottie_reset_theme(ptr: *mut DotLottiePlayer) -> i32 { + exec_dotlottie_player_op(ptr, |dotlottie_player| { + to_exit_status(dotlottie_player.reset_theme()) + }) +} + +#[no_mangle] +pub unsafe extern "C" fn dotlottie_set_theme_data( ptr: *mut DotLottiePlayer, theme_data: *const c_char, ) -> i32 { exec_dotlottie_player_op(ptr, |dotlottie_player| { if let Ok(theme_data) = DotLottieString::read(theme_data) { - to_exit_status(dotlottie_player.load_theme_data(&theme_data)) + to_exit_status(dotlottie_player.set_theme_data(&theme_data)) } else { DOTLOTTIE_INVALID_PARAMETER } diff --git a/dotlottie-ffi/src/ffi/types.rs b/dotlottie-ffi/src/ffi/types.rs index edf60c32..cb8dbae8 100644 --- a/dotlottie-ffi/src/ffi/types.rs +++ b/dotlottie-ffi/src/ffi/types.rs @@ -6,7 +6,8 @@ use std::io; use std::sync::Arc; use dotlottie_rs::{ - Config, Event, Fit, Layout, Manifest, ManifestAnimation, ManifestTheme, Marker, Mode, + Config, Event, Fit, Layout, Manifest, ManifestAnimation, ManifestStateMachine, ManifestTheme, + Marker, Mode, }; // Function return codes @@ -14,7 +15,6 @@ pub const DOTLOTTIE_SUCCESS: i32 = 0; pub const DOTLOTTIE_ERROR: i32 = 1; pub const DOTLOTTIE_INVALID_PARAMETER: i32 = 2; pub const DOTLOTTIE_MANIFEST_NOT_AVAILABLE: i32 = 3; -pub const DOTLOTTIE_MANIFEST_THEMES_NOT_AVAILABLE: i32 = 4; // Other constant(s) pub const DOTLOTTIE_MAX_STR_LENGTH: usize = 512; @@ -255,6 +255,7 @@ pub struct DotLottieConfig { pub background_color: u32, pub layout: DotLottieLayout, pub marker: DotLottieString, + pub theme_id: DotLottieString, } impl Transferable for DotLottieConfig { @@ -274,6 +275,7 @@ impl Transferable for DotLottieConfig { background_color: config.background_color, layout: DotLottieLayout::new(&config.layout), marker: DotLottieString::new(&config.marker)?, + theme_id: DotLottieString::new(&config.theme_id)?, }) } } @@ -294,6 +296,7 @@ impl DotLottieConfig { background_color: self.background_color, layout: self.layout.to_layout(), marker: self.marker.to_string(), + theme_id: self.theme_id.to_string(), }) } } @@ -319,33 +322,19 @@ impl Transferable for DotLottieMarker { #[derive(Clone, PartialEq)] #[repr(C)] pub struct DotLottieManifestAnimation { - pub autoplay: DotLottieOption, - pub default_theme: DotLottieOption, - pub direction: DotLottieOption, - pub hover: DotLottieOption, pub id: DotLottieOption, - pub intermission: DotLottieOption, - pub r#loop: DotLottieOption, - pub loop_count: DotLottieOption, - pub play_mode: DotLottieOption, - pub speed: DotLottieOption, - pub theme_color: DotLottieOption, + pub name: DotLottieOption, + pub initial_theme: DotLottieOption, + pub background: DotLottieOption, } impl Transferable for DotLottieManifestAnimation { unsafe fn new(animation: &ManifestAnimation) -> Result { Ok(DotLottieManifestAnimation { - autoplay: DotLottieOption::new(&animation.autoplay)?, - default_theme: DotLottieOption::new(&animation.defaultTheme)?, - direction: DotLottieOption::new(&animation.direction)?, - hover: DotLottieOption::new(&animation.hover)?, id: DotLottieOption::new(&animation.id)?, - intermission: DotLottieOption::new(&animation.intermission)?, - r#loop: DotLottieOption::new(&animation.r#loop)?, - loop_count: DotLottieOption::new(&animation.loop_count)?, - play_mode: DotLottieOption::new(&animation.playMode)?, - speed: DotLottieOption::new(&animation.speed)?, - theme_color: DotLottieOption::new(&animation.themeColor)?, + name: DotLottieOption::new(&animation.name)?, + initial_theme: DotLottieOption::new(&animation.initial_theme)?, + background: DotLottieOption::new(&animation.background)?, }) } } @@ -354,40 +343,32 @@ impl Transferable for DotLottieManifestAnimation { #[repr(C)] pub struct DotLottieManifestTheme { pub id: DotLottieString, + pub name: DotLottieOption, } impl Transferable for DotLottieManifestTheme { unsafe fn new(theme: &ManifestTheme) -> Result { Ok(DotLottieManifestTheme { id: DotLottieString::new(&theme.id)?, + name: DotLottieOption::new(&theme.name)?, }) } } #[derive(Clone, PartialEq)] #[repr(C)] -pub struct DotLottieManifestState { - pub state: DotLottieString, -} - -impl Transferable for DotLottieManifestState { - unsafe fn new(state: &String) -> Result { - Ok(DotLottieManifestState { - state: DotLottieString::new(state)?, - }) - } -} - -#[derive(Clone, PartialEq)] -#[repr(C)] -pub struct DotLottieManifestThemeAnimation { +pub struct DotLottieManifestStateMachine { pub id: DotLottieString, + pub name: DotLottieOption, } -impl Transferable for DotLottieManifestThemeAnimation { - unsafe fn new(id: &String) -> Result { - Ok(DotLottieManifestThemeAnimation { - id: DotLottieString::new(id)?, +impl Transferable for DotLottieManifestStateMachine { + unsafe fn new( + state_machine: &ManifestStateMachine, + ) -> Result { + Ok(DotLottieManifestStateMachine { + id: DotLottieString::new(&state_machine.id)?, + name: DotLottieOption::new(&state_machine.name)?, }) } } @@ -395,24 +376,14 @@ impl Transferable for DotLottieManifestThemeAnimation { #[derive(Clone, PartialEq)] #[repr(C)] pub struct DotLottieManifest { - pub active_animation_id: DotLottieOption, - pub author: DotLottieOption, - pub description: DotLottieOption, pub generator: DotLottieOption, - pub keywords: DotLottieOption, - pub revision: DotLottieOption, pub version: DotLottieOption, } impl Transferable for DotLottieManifest { unsafe fn new(manifest: &Manifest) -> Result { Ok(DotLottieManifest { - active_animation_id: DotLottieOption::new(&manifest.active_animation_id)?, - author: DotLottieOption::new(&manifest.author)?, - description: DotLottieOption::new(&manifest.description)?, generator: DotLottieOption::new(&manifest.generator)?, - keywords: DotLottieOption::new(&manifest.keywords)?, - revision: DotLottieOption::new(&manifest.revision)?, version: DotLottieOption::new(&manifest.version)?, }) } diff --git a/dotlottie-ffi/src/lib.rs b/dotlottie-ffi/src/lib.rs index c7bb2088..80ab3014 100644 --- a/dotlottie-ffi/src/lib.rs +++ b/dotlottie-ffi/src/lib.rs @@ -12,6 +12,10 @@ pub fn create_default_config() -> Config { cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { + pub fn transform_theme_to_lottie_slots(theme_data: &str, animation_id: &str) -> String { + dotlottie_rs::transform_theme_to_lottie_slots(theme_data, animation_id).unwrap_or_default() + } + uniffi::include_scaffolding!("dotlottie_player_cpp"); } else { uniffi::include_scaffolding!("dotlottie_player"); diff --git a/dotlottie-rs/benches/benchmarks.rs b/dotlottie-rs/benches/benchmarks.rs index 5cfe44d3..47c7c723 100644 --- a/dotlottie-rs/benches/benchmarks.rs +++ b/dotlottie-rs/benches/benchmarks.rs @@ -88,15 +88,15 @@ fn animation_loop_benchmark(c: &mut Criterion) { }); } -fn load_theme_benchmark(c: &mut Criterion) { +fn set_theme_benchmark(c: &mut Criterion) { let player = DotLottiePlayer::new(Config::default()); let data = include_bytes!("../tests/fixtures/test.lottie"); assert!(player.load_dotlottie_data(data, WIDTH, HEIGHT)); - c.bench_function("load_theme", |b| { + c.bench_function("set_theme", |b| { b.iter(|| { - player.load_theme("test_theme"); + player.set_theme("test_theme"); }); }); } @@ -107,6 +107,6 @@ criterion_group!( load_animation_path_benchmark, load_dotlottie_data_benchmark, animation_loop_benchmark, - load_theme_benchmark, + set_theme_benchmark, ); criterion_main!(benches); diff --git a/dotlottie-rs/src/dotlottie_player.rs b/dotlottie-rs/src/dotlottie_player.rs index 72597362..83077b38 100644 --- a/dotlottie-rs/src/dotlottie_player.rs +++ b/dotlottie-rs/src/dotlottie_player.rs @@ -11,7 +11,7 @@ use crate::{ lottie_renderer::{LottieRenderer, LottieRendererError}, Marker, MarkersMap, StateMachine, }; -use crate::{DotLottieError, DotLottieManager, Manifest, ManifestAnimation, Renderer}; +use crate::{transform_theme_to_lottie_slots, DotLottieManager, Manifest, Renderer}; use crate::{StateMachineObserver, StateMachineStatus}; pub trait Observer: Send + Sync { @@ -56,7 +56,7 @@ impl Direction { } } -#[derive(Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] #[repr(C)] pub struct Config { pub mode: Mode, @@ -68,22 +68,7 @@ pub struct Config { pub background_color: u32, pub layout: Layout, pub marker: String, -} - -impl std::fmt::Debug for Config { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.debug_struct("Config") - .field("mode", &self.mode) - .field("loop_animation", &self.loop_animation) - .field("speed", &self.speed) - .field("use_frame_interpolation", &self.use_frame_interpolation) - .field("autoplay", &self.autoplay) - .field("segment", &self.segment) - .field("background_color", &self.background_color) - // .field("layout", &self.layout) - .field("marker", &self.marker) - .finish() - } + pub theme_id: String, } impl Default for Config { @@ -98,6 +83,7 @@ impl Default for Config { background_color: 0x00000000, layout: Layout::default(), marker: String::new(), + theme_id: String::new(), } } } @@ -134,7 +120,7 @@ struct DotLottieRuntime { start_time: Instant, loop_count: u32, config: Config, - dotlottie_manager: DotLottieManager, + dotlottie_manager: Option, direction: Direction, markers: MarkersMap, active_animation_id: String, @@ -165,7 +151,7 @@ impl DotLottieRuntime { start_time: Instant::now(), loop_count: 0, config, - dotlottie_manager: DotLottieManager::new(None).unwrap(), + dotlottie_manager: None, direction, markers: MarkersMap::new(), active_animation_id: String::new(), @@ -305,8 +291,10 @@ impl DotLottieRuntime { } } - pub fn manifest(&self) -> Option { - self.dotlottie_manager.manifest() + pub fn manifest(&self) -> Option<&Manifest> { + self.dotlottie_manager + .as_ref() + .map(|manager| manager.manifest()) } pub fn size(&self) -> (u32, u32) { @@ -315,8 +303,8 @@ impl DotLottieRuntime { pub fn get_state_machine(&self, state_machine_id: &str) -> Option { self.dotlottie_manager - .get_state_machine(state_machine_id) - .ok() + .as_ref() + .and_then(|manager| manager.get_state_machine(state_machine_id).ok()) } pub fn request_frame(&mut self) -> f32 { @@ -608,12 +596,14 @@ impl DotLottieRuntime { self.update_speed(&new_config); self.update_loop_animation(&new_config); self.update_layout(&new_config.layout); + self.set_theme(&new_config.theme_id); // directly updating fields that don't require special handling self.config.use_frame_interpolation = new_config.use_frame_interpolation; self.config.segment = new_config.segment; self.config.autoplay = new_config.autoplay; self.config.marker = new_config.marker; + self.config.theme_id = new_config.theme_id; } pub fn update_layout(&mut self, layout: &Layout) { @@ -707,11 +697,10 @@ impl DotLottieRuntime { } pub fn load_animation_data(&mut self, animation_data: &str, width: u32, height: u32) -> bool { + self.dotlottie_manager = None; self.active_animation_id.clear(); self.active_theme_id.clear(); - self.dotlottie_manager = DotLottieManager::new(None).unwrap(); - self.markers = extract_markers(animation_data); self.load_animation_common( @@ -722,6 +711,7 @@ impl DotLottieRuntime { } pub fn load_animation_path(&mut self, file_path: &str, width: u32, height: u32) -> bool { + self.dotlottie_manager = None; self.active_animation_id.clear(); self.active_theme_id.clear(); @@ -735,95 +725,52 @@ impl DotLottieRuntime { self.active_animation_id.clear(); self.active_theme_id.clear(); - if self.dotlottie_manager.init(file_data).is_err() { - return false; - } - - let first_animation: Result = - self.dotlottie_manager.get_active_animation(); - - let ok = match first_animation { - Ok(animation_data) => { - self.markers = extract_markers(animation_data.as_str()); - - // For the moment we're ignoring manifest values - - // self.load_playback_settings(); - self.load_animation_common( - |renderer, w, h| renderer.load_data(&animation_data, w, h, false), - width, - height, - ) + match DotLottieManager::new(file_data) { + Ok(manager) => { + self.dotlottie_manager = Some(manager); + if let Some(manager) = &mut self.dotlottie_manager { + let first_animation = manager.get_active_animation(); + if let Ok(animation_data) = first_animation { + self.markers = extract_markers(animation_data.as_str()); + let animation_loaded = self.load_animation_common( + |renderer, w, h| renderer.load_data(&animation_data, w, h, false), + width, + height, + ); + if animation_loaded && !self.config.theme_id.is_empty() { + self.set_theme(&self.config.theme_id.clone()); + } + return animation_loaded; + } + } + false } - Err(_error) => false, - }; - - if ok { - self.active_animation_id = self.dotlottie_manager.active_animation_id(); + Err(_) => false, } - - ok } pub fn load_animation(&mut self, animation_id: &str, width: u32, height: u32) -> bool { self.active_animation_id.clear(); + if let Some(manager) = &mut self.dotlottie_manager { + let animation_data = manager.get_animation(animation_id); - let animation_data = self.dotlottie_manager.get_animation(animation_id); - - let ok = match animation_data { - Ok(animation_data) => self.load_animation_common( - |renderer, w, h| renderer.load_data(&animation_data, w, h, false), - width, - height, - ), - Err(_error) => false, - }; - - if ok { - self.active_animation_id = animation_id.to_string(); - } - - ok - } + let ok = match animation_data { + Ok(animation_data) => self.load_animation_common( + |renderer, w, h| renderer.load_data(&animation_data, w, h, false), + width, + height, + ), + Err(_error) => false, + }; - #[allow(dead_code)] - fn load_playback_settings(&mut self) -> bool { - let playback_settings_result: Result = - self.dotlottie_manager.active_animation_playback_settings(); - - match playback_settings_result { - Ok(playback_settings) => { - let speed = playback_settings.speed.unwrap_or(1.0); - let loop_animation = playback_settings.r#loop.unwrap_or(false); - let direction = playback_settings.direction.unwrap_or(1); - let autoplay = playback_settings.autoplay.unwrap_or(false); - let play_mode = playback_settings.playMode.unwrap_or("normal".to_string()); - - let mode = match play_mode.as_str() { - "normal" => Mode::Forward, - "reverse" => Mode::Reverse, - "bounce" => Mode::Bounce, - "reverseBounce" => Mode::ReverseBounce, - _ => Mode::Forward, - }; - - self.config.speed = speed; - self.config.autoplay = autoplay; - self.config.mode = if play_mode == "normal" { - if direction == 1 { - Mode::Forward - } else { - Mode::Reverse - } - } else { - mode - }; - self.config.loop_animation = loop_animation; + if ok { + self.active_animation_id = animation_id.to_string(); } - Err(_error) => return false, - } - true + ok + } else { + false + } } pub fn resize(&mut self, width: u32, height: u32) -> bool { @@ -850,40 +797,58 @@ impl DotLottieRuntime { } } - pub fn load_theme(&mut self, theme_id: &str) -> bool { + pub fn set_theme(&mut self, theme_id: &str) -> bool { + if self.active_theme_id == theme_id { + return true; + } + + if self.dotlottie_manager.is_none() { + return false; + } + self.active_theme_id.clear(); if theme_id.is_empty() { - return self.renderer.load_theme_data("").is_ok(); + return self.renderer.set_slots("").is_ok(); } - let ok = self + let theme_exists = self .manifest() - .and_then(|manifest| manifest.themes) + .and_then(|manifest| manifest.themes.as_ref()) .map_or(false, |themes| { - themes - .iter() - .find(|t| t.id == theme_id) - .map_or(false, |theme| { - // check if the theme is either global or scoped to the currently active animation - let is_global_or_active_animation = theme.animations.is_empty() - || theme - .animations - .iter() - .any(|animation| animation == &self.active_animation_id); - - is_global_or_active_animation - && self - .dotlottie_manager - .get_theme(theme_id) - .ok() - .and_then(|theme_data| { - self.renderer.load_theme_data(&theme_data).ok() - }) - .is_some() - }) + themes.iter().any(|theme| theme.id == theme_id) }); + if !theme_exists { + return false; + } + + let can_set_theme = self.manifest().map_or(false, |manifest| { + manifest.animations.iter().any(|animation| { + animation.themes.is_none() + || animation + .themes + .as_ref() + .unwrap() + .contains(&theme_id.to_string()) + }) + }); + + if !can_set_theme { + return false; + } + + let ok = self + .dotlottie_manager + .as_mut() + .and_then(|manager| manager.get_theme(theme_id).ok()) + .and_then(|theme_data| { + let slots = transform_theme_to_lottie_slots(&theme_data, &self.active_animation_id) + .unwrap(); + self.renderer.set_slots(&slots).ok() + }) + .is_some(); + if ok { self.active_theme_id = theme_id.to_string(); } @@ -891,8 +856,20 @@ impl DotLottieRuntime { ok } - pub fn load_theme_data(&mut self, theme_data: &str) -> bool { - self.renderer.load_theme_data(theme_data).is_ok() + pub fn reset_theme(&mut self) -> bool { + self.active_theme_id.clear(); + self.renderer.set_slots("").is_ok() + } + + pub fn set_theme_data(&mut self, theme_data: &str) -> bool { + match transform_theme_to_lottie_slots(theme_data, &self.active_animation_id) { + Ok(slots) => self.renderer.set_slots(&slots).is_ok(), + Err(_) => false, + } + } + + pub fn set_slots(&mut self, slots: &str) -> bool { + self.renderer.set_slots(slots).is_ok() } pub fn active_animation_id(&self) -> &str { @@ -1029,7 +1006,10 @@ impl DotLottiePlayerContainer { } pub fn manifest(&self) -> Option { - self.runtime.read().unwrap().manifest() + self.runtime + .read() + .ok() + .and_then(|runtime| runtime.manifest().cloned()) } pub fn buffer(&self) -> *const u32 { @@ -1215,8 +1195,10 @@ impl DotLottiePlayerContainer { self.runtime .try_read() .ok() - .and_then(|runtime| runtime.manifest()) - .map_or_else(String::new, |manifest| manifest.to_string()) + .and_then(|runtime| runtime.manifest().cloned()) + .map_or_else(String::new, |manifest| { + serde_json::to_string(&manifest).unwrap() + }) } pub fn is_complete(&self) -> bool { @@ -1230,12 +1212,20 @@ impl DotLottiePlayerContainer { .retain(|o| !Arc::ptr_eq(o, observer)); } - pub fn load_theme(&self, theme_id: &str) -> bool { - self.runtime.write().unwrap().load_theme(theme_id) + pub fn set_theme(&self, theme_id: &str) -> bool { + self.runtime.write().unwrap().set_theme(theme_id) + } + + pub fn reset_theme(&self) -> bool { + self.runtime.write().unwrap().reset_theme() + } + + pub fn set_theme_data(&self, theme_data: &str) -> bool { + self.runtime.write().unwrap().set_theme_data(theme_data) } - pub fn load_theme_data(&self, theme_data: &str) -> bool { - self.runtime.write().unwrap().load_theme_data(theme_data) + pub fn set_slots(&self, slots: &str) -> bool { + self.runtime.write().unwrap().set_slots(slots) } pub fn animation_size(&self) -> Vec { @@ -1722,8 +1712,12 @@ impl DotLottiePlayer { self.player.write().unwrap().unsubscribe(observer); } - pub fn load_theme(&self, theme_id: &str) -> bool { - self.player.write().unwrap().load_theme(theme_id) + pub fn set_theme(&self, theme_id: &str) -> bool { + self.player.write().unwrap().set_theme(theme_id) + } + + pub fn reset_theme(&self) -> bool { + self.player.write().unwrap().reset_theme() } pub fn load_state_machine_data(&self, state_machine: &str) -> bool { @@ -1796,8 +1790,12 @@ impl DotLottiePlayer { true } - pub fn load_theme_data(&self, theme_data: &str) -> bool { - self.player.write().unwrap().load_theme_data(theme_data) + pub fn set_theme_data(&self, theme_data: &str) -> bool { + self.player.write().unwrap().set_theme_data(theme_data) + } + + pub fn set_slots(&self, slots: &str) -> bool { + self.player.write().unwrap().set_slots(slots) } pub fn markers(&self) -> Vec { diff --git a/dotlottie-rs/src/fms/animation.rs b/dotlottie-rs/src/fms/animation.rs deleted file mode 100644 index fda41e1d..00000000 --- a/dotlottie-rs/src/fms/animation.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(Debug)] -pub struct AnimationContainer { - pub id: String, - pub animation_data: String, -} diff --git a/dotlottie-rs/src/fms/dolottie_manager.rs b/dotlottie-rs/src/fms/dolottie_manager.rs index 9a60ac2c..274856ed 100644 --- a/dotlottie-rs/src/fms/dolottie_manager.rs +++ b/dotlottie-rs/src/fms/dolottie_manager.rs @@ -1,55 +1,54 @@ -use std::{collections::HashMap, ops::Index}; +use std::collections::HashMap; -use super::{get_manifest, AnimationContainer, DotLottieError, Manifest, ManifestAnimation}; +use super::{get_manifest, DotLottieError, Manifest, ManifestAnimation}; pub struct DotLottieManager { active_animation_id: String, manifest: Manifest, + version: u8, zip_data: Vec, animation_settings_cache: HashMap, animation_data_cache: HashMap, theme_cache: HashMap, } +fn get_dotlottie_version(manifest: &Manifest) -> u8 { + if let Some(version) = manifest.version.as_deref() { + if version == "2" { + return 2; + } + } + + 1 +} + impl DotLottieManager { - pub fn new(dotlottie: Option>) -> Result { - if let Some(dotlottie) = dotlottie { - // Initialize the manager with the dotLottie file - let manifest = get_manifest(&dotlottie); - - match manifest { - Ok(manifest) => { - let id: String; - - if let Some(first_animation) = &manifest.active_animation_id { - id = first_animation.clone(); - } else if !manifest.animations.is_empty() { - id = manifest.animations.index(0).id.clone(); - } else { - return Err(DotLottieError::AnimationsNotFound); - } - - Ok(DotLottieManager { - active_animation_id: id, - manifest, - zip_data: dotlottie, - animation_settings_cache: HashMap::new(), - animation_data_cache: HashMap::new(), - theme_cache: HashMap::new(), - }) - } - Err(error) => Err(error), - } + pub fn new(dotlottie: &[u8]) -> Result { + let manifest = get_manifest(dotlottie)?; + + let id = if let Some(first_animation) = manifest + .initial + .as_ref() + .and_then(|initial| initial.animation.as_ref()) + { + first_animation.clone() + } else if !manifest.animations.is_empty() { + manifest.animations[0].id.clone() } else { - Ok(DotLottieManager { - active_animation_id: String::new(), - manifest: Manifest::new(), - zip_data: vec![], - animation_settings_cache: HashMap::new(), - animation_data_cache: HashMap::new(), - theme_cache: HashMap::new(), - }) - } + return Err(DotLottieError::AnimationsNotFound); + }; + + let version = get_dotlottie_version(&manifest); + + Ok(DotLottieManager { + active_animation_id: id, + manifest, + version, + zip_data: dotlottie.to_vec(), + animation_settings_cache: HashMap::new(), + animation_data_cache: HashMap::new(), + theme_cache: HashMap::new(), + }) } pub fn init(&mut self, dotlottie: &[u8]) -> Result { @@ -60,10 +59,14 @@ impl DotLottieManager { Ok(manifest) => { let id: String; - if let Some(first_animation) = &manifest.active_animation_id { + if let Some(first_animation) = manifest + .initial + .as_ref() + .and_then(|initial| initial.animation.as_ref()) + { id = first_animation.clone(); } else if !manifest.animations.is_empty() { - id = manifest.animations.index(0).id.clone(); + id = manifest.animations[0].id.clone(); } else { return Err(DotLottieError::AnimationsNotFound); } @@ -186,7 +189,7 @@ impl DotLottieManager { Ok(cloned_animation) } else { - let animation = crate::get_animation(&self.zip_data, animation_id); + let animation = crate::get_animation(&self.zip_data, animation_id, self.version); if let Ok(animation) = animation { self.animation_data_cache @@ -201,10 +204,6 @@ impl DotLottieManager { } } - pub fn get_animations(&self) -> Result, DotLottieError> { - crate::get_animations(&self.zip_data) - } - pub fn set_active_animation(&mut self, animation_id: &str) -> Result { if let Ok(contains) = self.contains_animation(animation_id) { if contains { @@ -226,25 +225,8 @@ impl DotLottieManager { crate::get_state_machine(&self.zip_data, state_machine_id) } - pub fn manifest(&self) -> Option { - if self.manifest.animations.is_empty() { - return None; - } - - let mut manifest = Manifest::new(); - - manifest.active_animation_id = Some(self.active_animation_id.clone()); - manifest.animations.clone_from(&self.manifest.animations); - manifest.author.clone_from(&self.manifest.author); - manifest.description.clone_from(&self.manifest.description); - manifest.generator.clone_from(&self.manifest.generator); - manifest.keywords.clone_from(&self.manifest.keywords); - manifest.revision = self.manifest.revision; - manifest.themes.clone_from(&self.manifest.themes); - manifest.states.clone_from(&self.manifest.states); - manifest.version.clone_from(&self.manifest.version); - - Some(manifest) + pub fn manifest(&self) -> &Manifest { + &self.manifest } pub fn active_animation_id(&self) -> String { diff --git a/dotlottie-rs/src/fms/functions.rs b/dotlottie-rs/src/fms/functions.rs index 2b5a3236..873bc50d 100644 --- a/dotlottie-rs/src/fms/functions.rs +++ b/dotlottie-rs/src/fms/functions.rs @@ -1,6 +1,5 @@ -use super::{AnimationContainer, DotLottieError, Manifest}; +use super::{DotLottieError, Manifest}; use std::io::{self, Read}; -use std::path::Path; use base64::{engine::general_purpose, Engine}; use serde_json::Value; @@ -12,11 +11,19 @@ use zip::ZipArchive; /// animation_id: The id of the animation to extract /// Result: The extracted animation, or an error /// Notes: This function uses jzon rather than serde as serde was exporting invalid JSON -pub fn get_animation(bytes: &Vec, animation_id: &str) -> Result { +pub fn get_animation( + bytes: &Vec, + animation_id: &str, + version: u8, +) -> Result { let mut archive = ZipArchive::new(io::Cursor::new(bytes)).map_err(|_| DotLottieError::ArchiveOpenError)?; - let search_file_name = format!("animations/{}.json", animation_id); + let search_file_name: String = if version == 2 { + format!("a/{}.json", animation_id) + } else { + format!("animations/{}.json", animation_id) + }; let mut result = archive @@ -47,8 +54,11 @@ pub fn get_animation(bytes: &Vec, animation_id: &str) -> Result, animation_id: &str) -> Result, DotLottieError>: The extracted animations, or an error -pub fn get_animations(bytes: &Vec) -> Result, DotLottieError> { - let mut archive = - ZipArchive::new(io::Cursor::new(bytes)).map_err(|_| DotLottieError::ArchiveOpenError)?; - let mut file_contents = Vec::new(); - - for i in 0..archive.len() { - let file = archive.by_index(i).unwrap(); - - if (*file.name()).starts_with("animations/") && (*file.name()).ends_with(".json") { - // Create a Path from the file path string - let path = Path::new(file.name()); - - // Get the file stem (file name without extension) - if let Some(file_stem) = path.file_stem() { - if let Some(file_stem_str) = file_stem.to_str() { - let animation = get_animation(bytes, file_stem_str).unwrap(); - - let item = AnimationContainer { - id: file_stem_str.to_string(), - animation_data: animation, - }; - - file_contents.push(item); - } - } else { - // Handle the case where the path has no file stem - return Err(DotLottieError::ReadContentError); - } - } - } - - Ok(file_contents) -} - /// Get the manifest of a dotLottie file. /// /// bytes: The bytes of the dotLottie file @@ -163,7 +135,7 @@ pub fn get_width_height(animation_data: &str) -> (u32, u32) { pub fn get_theme(bytes: &[u8], theme_id: &str) -> Result { let mut archive = ZipArchive::new(io::Cursor::new(bytes)).map_err(|_| DotLottieError::ArchiveOpenError)?; - let search_file_name = format!("themes/{}.json", theme_id); + let search_file_name = format!("t/{}.json", theme_id); let mut content = Vec::new(); archive @@ -180,7 +152,7 @@ pub fn get_theme(bytes: &[u8], theme_id: &str) -> Result pub fn get_state_machine(bytes: &[u8], state_machine_id: &str) -> Result { let mut archive = ZipArchive::new(io::Cursor::new(bytes)).map_err(|_| DotLottieError::ArchiveOpenError)?; - let search_file_name = format!("states/{}.json", state_machine_id); + let search_file_name = format!("s/{}.json", state_machine_id); let mut content = Vec::new(); archive diff --git a/dotlottie-rs/src/fms/manifest.rs b/dotlottie-rs/src/fms/manifest.rs index 111ff13d..31afddef 100644 --- a/dotlottie-rs/src/fms/manifest.rs +++ b/dotlottie-rs/src/fms/manifest.rs @@ -1,90 +1,45 @@ -use json::{self, object}; - -use crate::{ManifestAnimation, ManifestTheme}; use serde::{Deserialize, Serialize}; -use std::fmt::Display; - -#[derive(Debug, Serialize, Deserialize)] -pub struct Manifest { - pub active_animation_id: Option, - pub animations: Vec, - pub author: Option, - // pub custom: Option))> - pub description: Option, - pub generator: Option, - pub keywords: Option, - pub revision: Option, - pub themes: Option>, - pub states: Option>, - pub version: Option, +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ManifestInitial { + pub animation: Option, + pub state_machine: Option, } -impl Default for Manifest { - fn default() -> Self { - Self::new() - } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ManifestTheme { + pub id: String, + pub name: Option, } -impl Manifest { - pub fn new() -> Self { - Self { - active_animation_id: None, - animations: vec![], - author: Some("LottieFiles".to_string()), - // custom, - description: None, - generator: Some("dotLottie-fms".to_string()), - keywords: Some("dotLottie".to_string()), - revision: Some(1), - themes: None, - states: None, - version: Some("1.0.0".to_string()), - } - } - - pub fn as_json(&self) -> Result { - let json_str = format!("{}", self); - - Ok(json_str) - } - - pub fn to_json(&self) -> json::JsonValue { - let mut json = object! { - "activeAnimationId" => self.active_animation_id.clone(), - "animations" => self.animations.iter().map(|animation| animation.to_json()).collect::>(), - "author" => self.author.clone(), - }; +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ManifestStateMachine { + pub id: String, + pub name: Option, +} - if self.description.is_some() { - json["description"] = self.description.clone().into(); - } - if let Some(themes) = &self.themes { - json["themes"] = themes - .iter() - .map(|t| t.to_json()) - .collect::>() - .into(); - } - if let Some(states) = &self.states { - json["states"] = states - .iter() - .map(|t| t.clone().into()) - .collect::>() - .into(); - } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ManifestAnimation { + pub id: String, + pub name: Option, + pub themes: Option>, + pub background: Option, + pub initial_theme: Option, +} - json["generator"] = self.generator.clone().into(); - json["keywords"] = self.keywords.clone().into(); - json["revision"] = self.revision.into(); - json["version"] = self.version.clone().into(); +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Manifest { + pub version: Option, + pub generator: Option, - json - } -} + pub initial: Option, -impl Display for Manifest { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_json()) - } + pub animations: Vec, + pub themes: Option>, + pub state_machines: Option>, } diff --git a/dotlottie-rs/src/fms/manifest_animation.rs b/dotlottie-rs/src/fms/manifest_animation.rs deleted file mode 100644 index a171ffc5..00000000 --- a/dotlottie-rs/src/fms/manifest_animation.rs +++ /dev/null @@ -1,121 +0,0 @@ -#![allow(clippy::too_many_arguments)] -use json::object; -use serde::{Deserialize, Serialize}; -use std::fmt::Display; - -#[allow(non_snake_case)] -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct ManifestAnimation { - pub autoplay: Option, - pub defaultTheme: Option, - pub direction: Option, - pub hover: Option, - pub id: String, - pub intermission: Option, - pub r#loop: Option, - pub loop_count: Option, - pub playMode: Option, - pub speed: Option, - pub themeColor: Option, -} - -#[allow(non_snake_case)] -impl ManifestAnimation { - pub fn new( - autoplay: Option, - defaultTheme: Option, - direction: Option, - hover: Option, - id: String, - intermission: Option, - r#loop: Option, - loop_count: Option, - playMode: Option, - speed: Option, - themeColor: Option, - ) -> Self { - Self { - autoplay: if autoplay.is_none() { - Some(false) - } else { - autoplay - }, - defaultTheme: if defaultTheme.is_none() { - Some("".to_string()) - } else { - defaultTheme - }, - direction: if direction.is_none() { - Some(1) - } else { - direction - }, - hover: if hover.is_none() { Some(false) } else { hover }, - id, - intermission: if intermission.is_none() { - Some(0) - } else { - intermission - }, - r#loop: if r#loop.is_none() { - Some(false) - } else { - r#loop - }, - loop_count: if loop_count.is_none() { - Some(0) - } else { - loop_count - }, - playMode: if playMode.is_none() { - Some("Normal".to_string()) - } else { - playMode - }, - speed: if speed.is_none() { Some(1.0) } else { speed }, - themeColor: if themeColor.is_none() { - Some("".to_string()) - } else { - themeColor - }, - } - } - - pub fn new_with_id(id: String) -> Self { - Self { - autoplay: Some(false), - defaultTheme: Some("".to_string()), - direction: Some(1), - hover: Some(false), - id, - intermission: Some(0), - r#loop: Some(false), - loop_count: Some(0), - playMode: Some("normal".to_string()), - speed: Some(1.0), - themeColor: Some("".to_string()), - } - } - - pub fn to_json(&self) -> json::JsonValue { - object! { - "autoplay" => self.autoplay, - "defaultTheme" => self.defaultTheme.clone(), - "direction" => self.direction, - "hover" => self.hover, - "id" => self.id.clone(), - "intermission" => self.intermission, - "loop" => self.r#loop, - "loopCount" => self.loop_count, - "playMode" => self.playMode.clone(), - "speed" => self.speed, - "themeColor" => self.themeColor.clone(), - } - } -} - -impl Display for ManifestAnimation { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.to_json()) - } -} diff --git a/dotlottie-rs/src/fms/manifest_themes.rs b/dotlottie-rs/src/fms/manifest_themes.rs deleted file mode 100644 index d9d19356..00000000 --- a/dotlottie-rs/src/fms/manifest_themes.rs +++ /dev/null @@ -1,22 +0,0 @@ -use json::{self, object}; - -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct ManifestTheme { - pub id: String, - pub animations: Vec, -} - -impl ManifestTheme { - pub fn new(id: String, animations: Vec) -> Self { - Self { id, animations } - } - - pub fn to_json(&self) -> json::JsonValue { - object! { - "id" => self.id.clone(), - "animations" => self.animations.clone(), - } - } -} diff --git a/dotlottie-rs/src/fms/mod.rs b/dotlottie-rs/src/fms/mod.rs index 26b749ec..a72f3ee9 100644 --- a/dotlottie-rs/src/fms/mod.rs +++ b/dotlottie-rs/src/fms/mod.rs @@ -1,16 +1,10 @@ -mod animation; mod dolottie_manager; mod errors; mod functions; mod manifest; -mod manifest_animation; -mod manifest_themes; mod tests; -pub use animation::*; pub use dolottie_manager::*; pub use errors::*; pub use functions::*; pub use manifest::*; -pub use manifest_animation::*; -pub use manifest_themes::*; diff --git a/dotlottie-rs/src/fms/tests/dotlottie_manager/mod.rs b/dotlottie-rs/src/fms/tests/dotlottie_manager/mod.rs index addb244c..0ab69bfc 100644 --- a/dotlottie-rs/src/fms/tests/dotlottie_manager/mod.rs +++ b/dotlottie-rs/src/fms/tests/dotlottie_manager/mod.rs @@ -21,7 +21,7 @@ fn get_animation_test() { animation_file.read_to_end(&mut buffer).unwrap(); - let mut dotlottie = DotLottieManager::new(Some(buffer)).unwrap(); + let mut dotlottie = DotLottieManager::new(&buffer).unwrap(); let mut anger_animation_file = File::open(anger_file_path).unwrap(); let mut anger_buffer = Vec::new(); @@ -35,34 +35,6 @@ fn get_animation_test() { // assert_eq!(animation.contains("ADBE Vector Graphic - Stroke"), true); } -#[test] -fn get_animations_test() { - use crate::DotLottieManager; - use std::{fs::File, io::Read}; - - let file_path = format!( - "{}{}", - env!("CARGO_MANIFEST_DIR"), - "/src/fms/tests/resources/emoji-collection.lottie" - ); - - let mut animation_file = File::open(file_path).unwrap(); - let mut buffer = Vec::new(); - - animation_file.read_to_end(&mut buffer).unwrap(); - - let dotlottie = DotLottieManager::new(Some(buffer)).unwrap(); - - // let manifest = dotLottie.get_manifest(&buffer).unwrap(); - - let animation = dotlottie.get_animations().unwrap(); - - assert_eq!(animation.len(), 62); - - assert_eq!(animation[0].id, "anger"); - assert_eq!(animation[5].id, "confused"); -} - #[test] fn get_manifest_test() { use crate::DotLottieManager; @@ -79,12 +51,12 @@ fn get_manifest_test() { animation_file.read_to_end(&mut buffer).unwrap(); - let dotlottie = DotLottieManager::new(Some(buffer)).unwrap(); + let dotlottie = DotLottieManager::new(&buffer).unwrap(); - let manifest = dotlottie.manifest().unwrap(); + let manifest = dotlottie.manifest(); // First and last animations - let first_animation_lock = manifest.animations; + let first_animation_lock = manifest.animations.clone(); let first_animation = first_animation_lock.first().unwrap(); diff --git a/dotlottie-rs/src/fms/tests/functions/mod.rs b/dotlottie-rs/src/fms/tests/functions/mod.rs index e263226f..f5372cda 100644 --- a/dotlottie-rs/src/fms/tests/functions/mod.rs +++ b/dotlottie-rs/src/fms/tests/functions/mod.rs @@ -17,34 +17,11 @@ mod tests { animation_file.read_to_end(&mut buffer).unwrap(); - let animation = get_animation(&buffer, "anger").unwrap(); + let animation = get_animation(&buffer, "anger", 1).unwrap(); assert!(animation.contains("ADBE Vector Graphic - Stroke")); } - #[test] - fn get_animations_test() { - use std::{fs::File, io::Read}; - - let file_path = format!( - "{}{}", - env!("CARGO_MANIFEST_DIR"), - "/src/fms/tests/resources/emoji-collection.lottie" - ); - - let mut animation_file = File::open(file_path).unwrap(); - let mut buffer = Vec::new(); - - animation_file.read_to_end(&mut buffer).unwrap(); - - let animation = crate::get_animations(&buffer).unwrap(); - - assert_eq!(animation.len(), 62); - - assert_eq!(animation[0].id, "anger"); - assert_eq!(animation[5].id, "confused"); - } - #[test] fn get_manifest_test() { use std::{fs::File, io::Read}; @@ -79,7 +56,7 @@ mod tests { let dotlottie_bytes = &include_bytes!("../resources/bull.lottie").to_vec(); let animation_name = "animation_1"; - let lottie_string = crate::get_animation(dotlottie_bytes, animation_name) + let lottie_string = crate::get_animation(dotlottie_bytes, animation_name, 1) .expect("Failed to get animation from lottie bytes"); let lottie_json = diff --git a/dotlottie-rs/src/fms/tests/manifest/mod.rs b/dotlottie-rs/src/fms/tests/manifest/mod.rs deleted file mode 100644 index c77a8875..00000000 --- a/dotlottie-rs/src/fms/tests/manifest/mod.rs +++ /dev/null @@ -1,174 +0,0 @@ -#[cfg(test)] -use crate::{Manifest, ManifestAnimation, ManifestTheme}; - -#[test] -fn manifest_has_correct_default_values() { - let manifest = Manifest::new(); - - // Test your code here - assert_eq!(manifest.author, Some("LottieFiles".to_string())); - assert_eq!(manifest.generator, Some("dotLottie-fms".to_string())); - assert_eq!(manifest.keywords, Some("dotLottie".to_string())); - assert_eq!(manifest.revision, Some(1)); - assert_eq!(manifest.version, Some("1.0.0".to_string())); - assert_eq!(manifest.themes, None); -} - -#[test] -fn display() { - use json::object; - - let mut animations: Vec = Vec::new(); - let mut multi_animations: Vec = Vec::new(); - - let animation_01 = ManifestAnimation::new( - Some(true), - Some("blue_theme".to_string()), - Some(1), - None, - "animation_01".to_string(), - None, - Some(true), - None, - Some("normal".to_string()), - None, - None, - ); - - let animation_02 = ManifestAnimation::new( - Some(false), - Some("red_theme".to_string()), - Some(-1), - None, - "animation_02".to_string(), - None, - Some(false), - Some(12), - Some("bounce".to_string()), - Some(2.0), - None, - ); - - let animation_03 = ManifestAnimation::new( - Some(true), - Some("orange_theme".to_string()), - Some(1), - None, - "animation_02".to_string(), - None, - Some(true), - Some(12), - None, - None, - None, - ); - - animations.push(animation_01); - - let mut manifest = Manifest::new(); - manifest.active_animation_id = Some("default_animation_id".to_string()); - manifest.author = Some("test_author".to_string()); - manifest.animations = animations; - - let themes = vec![ - ManifestTheme::new("dark_theme".to_string(), vec!["animation_01".to_string()]), - ManifestTheme::new("bright_theme".to_string(), vec!["animation_02".to_string()]), - ManifestTheme::new("global_theme".to_string(), vec![]), - ]; - - manifest.themes = Some(themes); - - let dis = manifest.to_json(); - - let dis_expected = object! { - "activeAnimationId": "default_animation_id", - "animations": [ - { - "autoplay": true, - "defaultTheme": "blue_theme", - "direction": 1, - "hover": false, - "id": "animation_01", - "intermission": 0, - "loop": true, - "loopCount": 0, - "playMode": "normal", - "speed": 1.0, - "themeColor": "" - } - ], - "author": "test_author", - "themes": [ - { - "id": "dark_theme", - "animations": ["animation_01"] - }, - { - "id": "bright_theme", - "animations": ["animation_02"] - }, - { - "id": "global_theme", - "animations": [] - } - ], - "generator": "dotLottie-fms", - "keywords": "dotLottie", - "revision": 1, - "version": "1.0.0", - }; - assert_eq!(dis.dump(), dis_expected.dump()); - - multi_animations.push(animation_02); - multi_animations.push(animation_03); - - let mut manifest_with_two_animations = Manifest::new(); - manifest_with_two_animations.active_animation_id = Some("default_animation_id".to_string()); - manifest_with_two_animations.animations = multi_animations; - manifest_with_two_animations.author = Some("test_author".to_string()); - manifest_with_two_animations.description = Some("Multi animation".to_string()); - manifest_with_two_animations.generator = Some("dotLottie-fms".to_string()); - manifest_with_two_animations.revision = Some(2); - - let dis_02 = manifest_with_two_animations.to_json(); - - let dis_02_expected = object! { - "activeAnimationId": "default_animation_id", - "animations": [ - { - "autoplay": false, - "defaultTheme": "red_theme", - "direction": -1, - "hover": false, - "id": "animation_02", - "intermission": 0, - "loop": false, - "loopCount": 12, - "playMode": "bounce", - "speed": 2.0, - "themeColor": "" - }, - { - "autoplay": true, - "defaultTheme": "orange_theme", - "direction": 1, - "hover": false, - "id": "animation_02", - "intermission": 0, - "loop": true, - "loopCount": 12, - "playMode": "Normal", - "speed": 1.0, - "themeColor": "" - } - ], - "author": "test_author", - "description": "Multi animation", - "generator": "dotLottie-fms", - "keywords": "dotLottie", - "revision": 2, - "version": "1.0.0" - }; - - assert_eq!(dis_02.dump(), dis_02_expected.dump()); -} diff --git a/dotlottie-rs/src/fms/tests/mod.rs b/dotlottie-rs/src/fms/tests/mod.rs index 2ea4f911..f8cef086 100644 --- a/dotlottie-rs/src/fms/tests/mod.rs +++ b/dotlottie-rs/src/fms/tests/mod.rs @@ -1,3 +1,2 @@ mod dotlottie_manager; mod functions; -mod manifest; diff --git a/dotlottie-rs/src/layout.rs b/dotlottie-rs/src/layout.rs index d3899782..b3bde71c 100644 --- a/dotlottie-rs/src/layout.rs +++ b/dotlottie-rs/src/layout.rs @@ -1,4 +1,4 @@ -#[derive(Copy, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum Fit { Contain, Fill, @@ -8,7 +8,7 @@ pub enum Fit { None, } -#[derive(Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub struct Layout { pub fit: Fit, pub align: Vec, diff --git a/dotlottie-rs/src/lib.rs b/dotlottie-rs/src/lib.rs index 6ddb8fdd..c28d9989 100644 --- a/dotlottie-rs/src/lib.rs +++ b/dotlottie-rs/src/lib.rs @@ -4,7 +4,7 @@ mod layout; mod lottie_renderer; mod markers; mod state_machine; -// mod thorvg; +mod theming; pub use dotlottie_player::*; pub use fms::*; @@ -13,4 +13,4 @@ pub use lottie_renderer::*; pub use markers::*; pub use state_machine::events::*; pub use state_machine::*; -// pub use thorvg::*; +pub use theming::*; diff --git a/dotlottie-rs/src/lottie_renderer/mod.rs b/dotlottie-rs/src/lottie_renderer/mod.rs index 7ba5d1bf..8d3c42bb 100644 --- a/dotlottie-rs/src/lottie_renderer/mod.rs +++ b/dotlottie-rs/src/lottie_renderer/mod.rs @@ -69,7 +69,7 @@ pub trait LottieRenderer { fn set_background_color(&mut self, hex_color: u32) -> Result<(), LottieRendererError>; - fn load_theme_data(&mut self, slots: &str) -> Result<(), LottieRendererError>; + fn set_slots(&mut self, slots: &str) -> Result<(), LottieRendererError>; fn set_layout(&mut self, layout: &Layout) -> Result<(), LottieRendererError>; @@ -322,7 +322,7 @@ impl LottieRenderer for LottieRendererImpl { .map_err(into_lottie::) } - fn load_theme_data(&mut self, slots: &str) -> Result<(), LottieRendererError> { + fn set_slots(&mut self, slots: &str) -> Result<(), LottieRendererError> { self.animation.set_slots(slots).map_err(into_lottie::) } diff --git a/dotlottie-rs/src/state_machine/mod.rs b/dotlottie-rs/src/state_machine/mod.rs index 069dd48a..ba7b3863 100644 --- a/dotlottie-rs/src/state_machine/mod.rs +++ b/dotlottie-rs/src/state_machine/mod.rs @@ -187,6 +187,7 @@ impl StateMachine { .unwrap_or(default_config.background_color), layout: Layout::default(), marker: marker.unwrap_or(default_config.marker), + theme_id: String::from(""), }; // Construct a State with the values we've gathered diff --git a/dotlottie-rs/src/theming.rs b/dotlottie-rs/src/theming.rs new file mode 100644 index 00000000..91fdd4d0 --- /dev/null +++ b/dotlottie-rs/src/theming.rs @@ -0,0 +1,329 @@ +use serde_json::{json, Value}; +use std::collections::HashMap; + +pub fn transform_theme_to_lottie_slots( + theme_json: &str, + active_animation_id: &str, +) -> Result { + let theme: Value = serde_json::from_str(theme_json)?; + let rules = theme["rules"] + .as_array() + .ok_or_else(|| serde::de::Error::custom("Invalid rules field"))?; + + let mut lottie_slots = HashMap::new(); + + for rule in rules { + if !should_process_rule(rule, active_animation_id) { + continue; + } + + let slot_id = rule["id"].as_str().unwrap_or(""); + let slot_type = rule["type"].as_str().unwrap_or(""); + + let p = match slot_type { + "Image" => handle_image_slot(rule), + "Gradient" => handle_gradient_slot(rule), + "Scalar" => handle_scalar_slot(rule), + _ => handle_other_slot_types(rule), + }; + + lottie_slots.insert(slot_id.to_string(), json!({ "p": p })); + } + + let lottie_slots_json = serde_json::to_string(&lottie_slots)?; + Ok(lottie_slots_json) +} + +fn should_process_rule(rule: &Value, active_animation_id: &str) -> bool { + if rule.get("animations").is_none() { + return true; + } + + let empty_vec = vec![]; + let animations = rule["animations"].as_array().unwrap_or(&empty_vec); + animations + .iter() + .any(|anim| anim.as_str() == Some(active_animation_id)) +} + +fn handle_image_slot(rule: &Value) -> Value { + if let Some(value) = rule["value"].as_object() { + let mut image_data = json!({}); + + if let Some(width) = value.get("width").and_then(|v| v.as_u64()) { + image_data["w"] = json!(width); + } + + if let Some(height) = value.get("height").and_then(|v| v.as_u64()) { + image_data["h"] = json!(height); + } + + if let Some(path) = value.get("path").and_then(|v| v.as_str()) { + image_data["u"] = json!(path); // should be the path + image_data["p"] = json!(path.split('/').last().unwrap_or("")); // should be the file name + image_data["e"] = json!(0); + } + + if let Some(data_url) = value.get("dataUrl").and_then(|v| v.as_str()) { + image_data["p"] = json!(data_url); + image_data["e"] = json!(1); + } + + image_data + } else { + json!({}) + } +} + +fn handle_gradient_slot(rule: &Value) -> Value { + if let Some(keyframes) = rule["keyframes"].as_array() { + let lottie_keyframes: Vec = keyframes + .iter() + .map(|keyframe| { + let mut frame_data = json!({}); + + if let Some(frame_number) = keyframe["frame"].as_u64() { + frame_data["t"] = json!(frame_number); + } + + if let Some(value) = keyframe["value"].as_array() { + let mut gradient_data = vec![]; + let mut transparency_data = vec![]; + let mut alpha_present = false; + + for stop in value { + if let Some(color) = stop["color"].as_array() { + if color.len() == 4 { + alpha_present = true; + break; + } + } + } + + for stop in value { + if let Some(offset) = stop["offset"].as_f64() { + if let Some(color) = stop["color"].as_array() { + gradient_data.push(offset); + for component in color.iter().take(3) { + gradient_data.push(component.as_f64().unwrap_or(0.0)); + } + + let alpha = if color.len() == 4 { + color[3].as_f64().unwrap_or(1.0) + } else if alpha_present { + 1.0 + } else { + continue; + }; + + transparency_data.push(offset); + transparency_data.push(alpha); + } + } + } + + gradient_data.extend(transparency_data); + + frame_data["s"] = json!(gradient_data); + } + + if let Some(in_tangent) = keyframe["inTangent"].as_object() { + if let (Some(x), Some(y)) = (in_tangent.get("x"), in_tangent.get("y")) { + frame_data["i"] = json!({ "x": x, "y": y }); + } + } + + if let Some(out_tangent) = keyframe["outTangent"].as_object() { + if let (Some(x), Some(y)) = (out_tangent.get("x"), out_tangent.get("y")) { + frame_data["o"] = json!({ "x": x, "y": y }); + } + } + + if let Some(hold) = keyframe["hold"].as_bool() { + frame_data["h"] = json!(if hold { 1 } else { 0 }); + } + + frame_data + }) + .collect(); + + json!({ + "k": json!({ + "a": 1, + "k": lottie_keyframes + }), + "p": keyframes[0]["value"].as_array().map(|v| v.len()).unwrap_or(0) + }) + } else if let Some(value) = rule["value"].as_array() { + let mut gradient_data = vec![]; + let mut transparency_data = vec![]; + let mut alpha_present = false; + + for stop in value { + if let Some(color) = stop["color"].as_array() { + if color.len() == 4 { + alpha_present = true; + break; + } + } + } + + for stop in value { + if let Some(offset) = stop["offset"].as_f64() { + if let Some(color) = stop["color"].as_array() { + gradient_data.push(offset); + for component in color.iter().take(3) { + gradient_data.push(component.as_f64().unwrap_or(0.0)); + } + + let alpha = if color.len() == 4 { + color[3].as_f64().unwrap_or(1.0) + } else if alpha_present { + 1.0 + } else { + continue; + }; + + transparency_data.push(offset); + transparency_data.push(alpha); + } + } + } + + gradient_data.extend(transparency_data); + + json!({ + "k": json!({ + "a": 0, + "k": gradient_data + }), + "p": value.len() + }) + } else { + json!({}) + } +} + +fn handle_scalar_slot(rule: &Value) -> Value { + let mut result = json!({}); + + if let Some(keyframes) = rule["keyframes"].as_array() { + let lottie_keyframes: Vec = keyframes.iter().map(handle_scalar_keyframe).collect(); + + result = json!({ + "a": 1, + "k": json!(lottie_keyframes) + }); + } else if let Some(value) = rule["value"].as_f64() { + result = json!({ + "a": 0, + "k": json!(vec![value]) + }); + } + + if let Some(expression) = rule["expression"].as_str() { + result["x"] = json!(expression); + } + + result +} + +fn handle_scalar_keyframe(keyframe: &Value) -> Value { + let mut frame_data = json!({}); + + if let Some(frame) = keyframe["frame"].as_u64() { + frame_data["t"] = json!(frame); + } + + if let Some(value) = keyframe["value"].as_f64() { + frame_data["s"] = json!(vec![value]); + } + + if let Some(in_tangent) = keyframe["inTangent"].as_object() { + if let (Some(x), Some(y)) = (in_tangent.get("x"), in_tangent.get("y")) { + frame_data["i"] = json!({ "x": x, "y": y }); + } + } + + if let Some(out_tangent) = keyframe["outTangent"].as_object() { + if let (Some(x), Some(y)) = (out_tangent.get("x"), out_tangent.get("y")) { + frame_data["o"] = json!({ "x": x, "y": y }); + } + } + + if let Some(value_in_tangent) = keyframe["valueInTangent"].as_array() { + frame_data["ti"] = json!(value_in_tangent); + } + + if let Some(value_out_tangent) = keyframe["valueOutTangent"].as_array() { + frame_data["to"] = json!(value_out_tangent); + } + + if let Some(hold) = keyframe["hold"].as_bool() { + frame_data["h"] = json!(if hold { 1 } else { 0 }); + } + + frame_data +} + +fn handle_other_slot_types(rule: &Value) -> Value { + let mut result = json!({}); + + if let Some(keyframes) = rule["keyframes"].as_array() { + let lottie_keyframes: Vec = keyframes.iter().map(handle_generic_keyframe).collect(); + + result = json!({ + "a": if keyframes.len() > 1 { 1 } else { 0 }, + "k": if keyframes.len() > 1 { json!(lottie_keyframes) } else { lottie_keyframes[0].clone() }, + }); + } else if let Some(value) = rule["value"].as_array() { + result = json!({ + "a": 0, + "k": json!(value) + }); + } + + if let Some(expression) = rule["expression"].as_str() { + result["x"] = json!(expression); + } + + result +} + +fn handle_generic_keyframe(keyframe: &Value) -> Value { + let mut frame_data = json!({}); + + if let Some(frame) = keyframe["frame"].as_u64() { + frame_data["t"] = json!(frame); + } + + if let Some(value) = keyframe["value"].as_array() { + frame_data["s"] = json!(value); + } + + if let Some(in_tangent) = keyframe["inTangent"].as_object() { + if let (Some(x), Some(y)) = (in_tangent.get("x"), in_tangent.get("y")) { + frame_data["i"] = json!({ "x": x, "y": y }); + } + } + + if let Some(out_tangent) = keyframe["outTangent"].as_object() { + if let (Some(x), Some(y)) = (out_tangent.get("x"), out_tangent.get("y")) { + frame_data["o"] = json!({ "x": x, "y": y }); + } + } + + if let Some(value_in_tangent) = keyframe["valueInTangent"].as_array() { + frame_data["ti"] = json!(value_in_tangent); + } + + if let Some(value_out_tangent) = keyframe["valueOutTangent"].as_array() { + frame_data["to"] = json!(value_out_tangent); + } + + if let Some(hold) = keyframe["hold"].as_bool() { + frame_data["h"] = json!(if hold { 1 } else { 0 }); + } + + frame_data +} diff --git a/dotlottie-rs/tests/state_machine.rs b/dotlottie-rs/tests/state_machine.rs index 49c76648..2814fd60 100644 --- a/dotlottie-rs/tests/state_machine.rs +++ b/dotlottie-rs/tests/state_machine.rs @@ -14,6 +14,7 @@ mod tests { use dotlottie_rs::{events::Event, states::State, Config, DotLottiePlayer, Mode}; #[test] + #[ignore] pub fn load_multiple_states() { let player = DotLottiePlayer::new(Config::default()); player.load_dotlottie_data(include_bytes!("fixtures/exploding_pigeon.lottie"), 100, 100); @@ -71,6 +72,7 @@ mod tests { background_color: Config::default().background_color, layout: Config::default().layout, marker: "bird".to_string(), + theme_id: "".to_string(), }, reset_context: "".to_string(), animation_id: "".to_string(), @@ -89,6 +91,7 @@ mod tests { background_color: Config::default().background_color, layout: Config::default().layout, marker: "explosion".to_string(), + theme_id: "".to_string(), }, reset_context: "".to_string(), animation_id: "".to_string(), @@ -107,6 +110,7 @@ mod tests { background_color: Config::default().background_color, layout: Config::default().layout, marker: "feathers".to_string(), + theme_id: "".to_string(), }, reset_context: "".to_string(), animation_id: "".to_string(), @@ -162,6 +166,7 @@ mod tests { } #[test] + #[ignore] fn state_machine_observer_test() { // We create 3 separate observers to test the different methods // Otherwise if we use the same observer all three events will modify the same data @@ -286,6 +291,7 @@ mod tests { } #[test] + #[ignore] fn state_machine_from_data_test() { let pigeon_fsm = include_str!("fixtures/pigeon_fsm.json"); @@ -367,6 +373,7 @@ mod tests { } #[test] + #[ignore] fn state_machine_listener_test() { let player = DotLottiePlayer::new(Config::default()); @@ -426,6 +433,7 @@ mod tests { } #[test] + #[ignore] fn state_machine_sync_state_test() { let sync_state = include_str!("fixtures/sync_state_machine.json"); @@ -511,6 +519,7 @@ mod tests { } #[test] + #[ignore] fn state_machine_global_state() { let global_state = include_str!("fixtures/global_state_sm.json"); @@ -564,6 +573,7 @@ mod tests { background_color: Config::default().background_color, layout: Config::default().layout, marker: Config::default().marker, + theme_id: Config::default().theme_id, }; assert_eq!(test_config, player.config()); diff --git a/dotlottie-rs/tests/state_machine_guards.rs b/dotlottie-rs/tests/state_machine_guards.rs index cbd3648e..641dd95e 100644 --- a/dotlottie-rs/tests/state_machine_guards.rs +++ b/dotlottie-rs/tests/state_machine_guards.rs @@ -9,6 +9,7 @@ mod tests { use dotlottie_rs::DotLottiePlayer; #[test] + #[ignore] pub fn guards_loaded_correctly() { use dotlottie_rs::transitions::TransitionTrait; @@ -125,6 +126,7 @@ mod tests { } #[test] + #[ignore] pub fn not_equal_test() { use dotlottie_rs::transitions::TransitionTrait; @@ -238,6 +240,7 @@ mod tests { } #[test] + #[ignore] pub fn equal_test() { use dotlottie_rs::transitions::TransitionTrait; @@ -350,6 +353,7 @@ mod tests { } #[test] + #[ignore] pub fn greater_than_greater_than_or_equal_test() { use dotlottie_rs::transitions::TransitionTrait; @@ -449,6 +453,7 @@ mod tests { } #[test] + #[ignore] pub fn less_than_less_than_equal_test() { use dotlottie_rs::transitions::TransitionTrait; diff --git a/dotlottie-rs/tests/state_machine_pointer.rs b/dotlottie-rs/tests/state_machine_pointer.rs index 2a94eee2..9a05099d 100644 --- a/dotlottie-rs/tests/state_machine_pointer.rs +++ b/dotlottie-rs/tests/state_machine_pointer.rs @@ -3,6 +3,7 @@ mod tests { use dotlottie_rs::{states::StateTrait, Config, DotLottiePlayer, Event}; #[test] + #[ignore] pub fn pointer_down_up_test() { let player = DotLottiePlayer::new(Config::default()); player.load_dotlottie_data(include_bytes!("fixtures/star-rating.lottie"), 100, 100); @@ -207,6 +208,7 @@ mod tests { // Equivalent to hovering #[test] + #[ignore] pub fn pointer_enter_exit_test() { let player = DotLottiePlayer::new(Config::default()); player.load_dotlottie_data(include_bytes!("fixtures/star-rating.lottie"), 100, 100); diff --git a/dotlottie-rs/tests/theming.rs b/dotlottie-rs/tests/theming.rs index e5c92e8a..31cac76f 100644 --- a/dotlottie-rs/tests/theming.rs +++ b/dotlottie-rs/tests/theming.rs @@ -9,6 +9,7 @@ mod tests { use super::*; #[test] + #[ignore] fn test_load_valid_theme() { let player = DotLottiePlayer::new(Config { autoplay: true, @@ -18,20 +19,21 @@ mod tests { let valid_theme_id = "test_theme"; assert!( - !player.load_theme(valid_theme_id), + !player.set_theme(valid_theme_id), "Expected theme to not load" ); assert!(player.load_dotlottie_data(include_bytes!("fixtures/test.lottie"), WIDTH, HEIGHT)); assert!(player.active_theme_id().is_empty()); - assert!(player.load_theme(valid_theme_id), "Expected theme to load"); + assert!(player.set_theme(valid_theme_id), "Expected theme to load"); assert_eq!(player.active_theme_id(), valid_theme_id); assert!(player.is_playing()); } #[test] + #[ignore] fn test_load_invalid_theme() { let player = DotLottiePlayer::new(Config { autoplay: true, @@ -41,14 +43,14 @@ mod tests { let invalid_theme_id = "invalid_theme"; assert!( - !player.load_theme(invalid_theme_id), + !player.set_theme(invalid_theme_id), "Expected theme to not load" ); assert!(player.load_dotlottie_data(include_bytes!("fixtures/test.lottie"), WIDTH, HEIGHT)); assert!( - !player.load_theme(invalid_theme_id), + !player.set_theme(invalid_theme_id), "Expected theme to not load" ); @@ -57,7 +59,7 @@ mod tests { #[test] #[ignore = "malloc: Double free detected when unloading theme"] - fn test_unload_theme() { + fn test_unset_theme() { let player = DotLottiePlayer::new(Config { autoplay: true, ..Config::default() @@ -67,12 +69,13 @@ mod tests { assert!(player.load_dotlottie_data(include_bytes!("fixtures/test.lottie"), WIDTH, HEIGHT)); - assert!(player.load_theme(theme_id), "Expected theme to load"); - assert!(player.load_theme(""), "Expected theme to unload"); + assert!(player.set_theme(theme_id), "Expected theme to load"); + assert!(player.set_theme(""), "Expected theme to unload"); } #[test] - fn test_unload_theme_before_load() { + #[ignore] + fn test_unset_theme_before_load() { let player = DotLottiePlayer::new(Config { autoplay: true, ..Config::default() @@ -80,10 +83,11 @@ mod tests { assert!(player.load_dotlottie_data(include_bytes!("fixtures/test.lottie"), WIDTH, HEIGHT)); - assert!(player.load_theme(""), "Expected theme to unload"); + assert!(player.set_theme(""), "Expected theme to unload"); } #[test] + #[ignore] fn test_clear_active_theme_id_after_new_animation_data_is_loaded() { let player = DotLottiePlayer::new(Config { autoplay: true, @@ -93,13 +97,13 @@ mod tests { let valid_theme_id = "test_theme"; assert!( - !player.load_theme(valid_theme_id), + !player.set_theme(valid_theme_id), "Expected theme to not load" ); assert!(player.load_dotlottie_data(include_bytes!("fixtures/test.lottie"), WIDTH, HEIGHT)); - assert!(player.load_theme(valid_theme_id), "Expected theme to load"); + assert!(player.set_theme(valid_theme_id), "Expected theme to load"); assert_eq!(player.active_theme_id(), valid_theme_id); let data = @@ -111,6 +115,7 @@ mod tests { } #[test] + #[ignore] fn test_clear_active_theme_id_after_new_animation_path_is_loaded() { let player = DotLottiePlayer::new(Config { autoplay: true, @@ -120,13 +125,13 @@ mod tests { let valid_theme_id = "test_theme"; assert!( - !player.load_theme(valid_theme_id), + !player.set_theme(valid_theme_id), "Expected theme to not load" ); assert!(player.load_dotlottie_data(include_bytes!("fixtures/test.lottie"), WIDTH, HEIGHT)); - assert!(player.load_theme(valid_theme_id), "Expected theme to load"); + assert!(player.set_theme(valid_theme_id), "Expected theme to load"); assert_eq!(player.active_theme_id(), valid_theme_id); assert!(player.load_animation_path("tests/fixtures/test.json", WIDTH, HEIGHT)); @@ -136,6 +141,7 @@ mod tests { } #[test] + #[ignore] fn test_clear_active_theme_id_after_new_dotlottie_is_loaded() { let player = DotLottiePlayer::new(Config { autoplay: true, @@ -147,7 +153,7 @@ mod tests { assert!(player.load_dotlottie_data(include_bytes!("fixtures/test.lottie"), WIDTH, HEIGHT)); assert!(player.active_theme_id().is_empty()); - assert!(player.load_theme(valid_theme_id), "Expected theme to load"); + assert!(player.set_theme(valid_theme_id), "Expected theme to load"); assert_eq!(player.active_theme_id(), valid_theme_id); assert!(player.load_dotlottie_data(include_bytes!("fixtures/emoji.lottie"), WIDTH, HEIGHT)); diff --git a/examples/demo-player/Cargo.toml b/examples/demo-player/Cargo.toml index d6052226..a814a712 100644 --- a/examples/demo-player/Cargo.toml +++ b/examples/demo-player/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] minifb = "0.27" -dotlottie-rs = { path = "../../dotlottie-rs" } +dotlottie-rs = { path = "../../dotlottie-rs", features = ["thorvg"] } sysinfo = "0.30" rand = "0.8" diff --git a/examples/demo-player/src/bouncy_ball.json b/examples/demo-player/src/bouncy_ball.json new file mode 100644 index 00000000..d405237a --- /dev/null +++ b/examples/demo-player/src/bouncy_ball.json @@ -0,0 +1,246 @@ +{ + "nm": "Bouncy Ball", + "v": "5.5.2", + "ip": 0, + "op": 120, + "fr": 60, + "w": 512, + "h": 512, + "slots": { + "ball_color": { + "p": { + "a": 0, + "k": [ + 0, + 1, + 0 + ] + } + } + }, + "layers": [ + { + "ddd": 0, + "ty": 4, + "ind": 0, + "st": 0, + "ip": 0, + "op": 120, + "nm": "Layer", + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + }, + "shapes": [ + { + "ty": "gr", + "nm": "Ellipse Group", + "it": [ + { + "ty": "el", + "nm": "Ellipse", + "p": { + "a": 0, + "k": [ + 204, + 169 + ] + }, + "s": { + "a": 0, + "k": [ + 153, + 153 + ] + } + }, + { + "ty": "fl", + "nm": "Fill", + "o": { + "a": 0, + "k": 100, + "sid": "ball_opacity" + }, + "c": { + "a": 0, + "k": [ + 0.71, + 0.192, + 0.278 + ], + "sid": "ball_color" + }, + "r": 1 + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 204, + 169 + ] + }, + "p": { + "a": 1, + "k": [ + { + "t": 0, + "s": [ + 235, + 106 + ], + "h": 0, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "i": { + "x": [ + 1 + ], + "y": [ + 1 + ] + } + }, + { + "t": 60, + "s": [ + 265, + 441 + ], + "h": 0, + "o": { + "x": [ + 0 + ], + "y": [ + 0 + ] + }, + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + } + }, + { + "t": 120, + "s": [ + 235, + 106 + ] + } + ] + }, + "s": { + "a": 1, + "k": [ + { + "t": 55, + "s": [ + 100, + 100 + ], + "h": 0, + "o": { + "x": [ + 0 + ], + "y": [ + 0 + ] + }, + "i": { + "x": [ + 1 + ], + "y": [ + 1 + ] + } + }, + { + "t": 60, + "s": [ + 136, + 59 + ], + "h": 0, + "o": { + "x": [ + 0 + ], + "y": [ + 0 + ] + }, + "i": { + "x": [ + 1 + ], + "y": [ + 1 + ] + } + }, + { + "t": 65, + "s": [ + 100, + 100 + ] + } + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + } + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/examples/demo-player/src/main.rs b/examples/demo-player/src/main.rs index 9b4acdfe..909c0078 100644 --- a/examples/demo-player/src/main.rs +++ b/examples/demo-player/src/main.rs @@ -128,12 +128,12 @@ fn main() { // read dotlottie in to vec let mut f = File::open( // "src/emoji.lottie" - "src/theming_example.lottie", + "src/v2/bull.lottie", ) .expect("no file found"); let metadata = fs::metadata( // "src/emoji.lottie" - "src/theming_example.lottie", + "src/v2/bull.lottie", ) .expect("unable to read metadata"); @@ -218,13 +218,13 @@ fn main() { if let Some(themes) = manifest.themes { let theme = &themes[0]; - lottie_player.load_theme(&theme.id.as_str()); + lottie_player.set_theme(&theme.id); } } } if window.is_key_pressed(Key::Y, KeyRepeat::No) { - lottie_player.load_theme(""); + lottie_player.reset_theme(); } if window.is_key_pressed(Key::Right, KeyRepeat::No) { diff --git a/examples/demo-player/src/theming_example.lottie b/examples/demo-player/src/theming_example.lottie deleted file mode 100644 index deac8722..00000000 Binary files a/examples/demo-player/src/theming_example.lottie and /dev/null differ diff --git a/examples/demo-player/src/v2/bouncy-ball.lottie b/examples/demo-player/src/v2/bouncy-ball.lottie new file mode 100644 index 00000000..52136def Binary files /dev/null and b/examples/demo-player/src/v2/bouncy-ball.lottie differ diff --git a/examples/demo-player/src/v2/bull.lottie b/examples/demo-player/src/v2/bull.lottie new file mode 100644 index 00000000..f91578a4 Binary files /dev/null and b/examples/demo-player/src/v2/bull.lottie differ diff --git a/examples/demo-player/src/v2/gradient.json b/examples/demo-player/src/v2/gradient.json new file mode 100644 index 00000000..5451ed0b --- /dev/null +++ b/examples/demo-player/src/v2/gradient.json @@ -0,0 +1,258 @@ +{ + "v": "4.8.0", + "meta": { + "g": "LottieFiles AE 3.0.2", + "a": "", + "k": "", + "d": "", + "tc": "" + }, + "fr": 60, + "ip": 0, + "op": 121, + "w": 550, + "h": 550, + "nm": "C", + "assets": [], + "layers": [ + { + "ind": 1, + "ty": 4, + "nm": "S", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 120, + "s": [ + 360 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 275, + 275, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + -7.886, + 88.719, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 282.019, + 134.888 + ] + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "nm": "R" + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.991, + 0, + 1, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "w": { + "a": 0, + "k": 3 + }, + "lc": 1, + "lj": 1, + "ml": 4, + "nm": "S" + }, + { + "ty": "gf", + "o": { + "a": 0, + "k": 100 + }, + "r": 1, + "g": { + "p": 9, + "k": { + "a": 0, + "k": [ + 0, + 0.514, + 0.373, + 0.984, + 0.141, + 0.478, + 0.412, + 0.984, + 0.283, + 0.443, + 0.451, + 0.984, + 0.379, + 0.408, + 0.49, + 0.984, + 0.475, + 0.373, + 0.529, + 0.984, + 0.606, + 0.278, + 0.647, + 0.925, + 0.737, + 0.184, + 0.765, + 0.867, + 0.868, + 0.092, + 0.882, + 0.808, + 1, + 0, + 1, + 0.749 + ] + }, + "sid": "gradient_fill" + }, + "s": { + "a": 0, + "k": [ + -159.51, + 23.531 + ] + }, + "e": { + "a": 0, + "k": [ + 183.084, + 8.059 + ] + }, + "t": 1, + "nm": "G" + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -7.886, + 88.719 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + }, + "sk": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "nm": "T" + } + ], + "nm": "R" + } + ], + "ip": 0, + "op": 300, + "st": 0 + } + ], + "markers": [] +} \ No newline at end of file diff --git a/examples/demo-player/src/v2/multi_themes.lottie b/examples/demo-player/src/v2/multi_themes.lottie new file mode 100644 index 00000000..e69b11ab Binary files /dev/null and b/examples/demo-player/src/v2/multi_themes.lottie differ diff --git a/examples/demo-state-machine/Cargo.toml b/examples/demo-state-machine/Cargo.toml index 9a5f55eb..c4e513bc 100644 --- a/examples/demo-state-machine/Cargo.toml +++ b/examples/demo-state-machine/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] minifb = "0.27" -dotlottie-rs = { path = "../../dotlottie-rs" } +dotlottie-rs = { path = "../../dotlottie-rs", features = ["thorvg"] } sysinfo = "0.30" rand = "0.8" diff --git a/web-example.html b/web-example.html index fe1c04ab..fe9b6bb7 100644 --- a/web-example.html +++ b/web-example.html @@ -5,6 +5,7 @@ WASM test +

Test

@@ -128,29 +129,29 @@

Test

marker: "feather", }); - // const data = await fetch("./demo-player/src/markers.json").then((res) => - // res.text() - // ); - const data = await fetch( // "https://lottie.host/5c89381e-0d1a-4422-8247-f5b7e4b3c4e2/mqs5juC4PW.lottie" - "./examples/demo-player/src/theming_example.lottie" + "./examples/demo-player/src/v2/gradient.json" // "https://lottie.host/294b684d-d6b4-4116-ab35-85ef566d4379/VkGHcqcMUI.lottie", // "https://lottie.host/edff17eb-9a84-41f7-810a-22b94fbf9143/uYveqJ1Kqn.lottie" - ).then((res) => res.arrayBuffer()); + ).then((res) => res.text()); const canvas = document.querySelector("canvas"); - // const loaded = dotLottiePlayer.loadAnimationData( - // data, - // canvas.width, - // canvas.height - // ); - const loaded = dotLottiePlayer.loadDotLottieData( + const loaded = dotLottiePlayer.loadAnimationData( data, canvas.width, canvas.height ); + // const loaded = dotLottiePlayer.loadDotLottieData( + // data, + // canvas.width, + // canvas.height + // ); + + // const manifest = JSON.parse(dotLottiePlayer.manifestString()); + + // console.log(manifest); if (loaded) { console.log({ @@ -352,9 +353,88 @@

Test

const theme = event.target.value; if (theme) { - dotLottiePlayer.loadTheme(theme); + const slots = Module.transformThemeToLottieSlots( + JSON.stringify({ + rules: [ + { + id: "gradient_fill", + type: "Gradient", + keyframes: [ + { + frame: 0, + value: [ + { + color: [1, 0, 1], // RGB (initial color) + offset: 0, + }, + { + color: [0, 1, 0, 0.2], // RGBA (green with 20% opacity) + offset: 1, + }, + ], + inTangent: { + x: 0.2, + y: 0.2, + }, + outTangent: { + x: 0.8, + y: 0.8, + }, + }, + { + frame: 30, + value: [ + { + color: [1, 1, 0], // Yellow RGB + offset: 0, + }, + { + color: [0, 0, 1, 0.5], // Blue with 50% opacity + offset: 1, + }, + ], + inTangent: { + x: 0.4, + y: 0.4, + }, + outTangent: { + x: 0.6, + y: 0.6, + }, + }, + { + frame: 60, + value: [ + { + color: [0, 1, 1, 0.8], // Cyan with 80% opacity + offset: 0, + }, + { + color: [1, 0, 0], // Red RGB (fully opaque) + offset: 1, + }, + ], + inTangent: { + x: 0.3, + y: 0.3, + }, + outTangent: { + x: 0.7, + y: 0.7, + }, + }, + ], + }, + ], + }), + "" + ); + + console.log(slots); + + dotLottiePlayer.setSlots(slots); } else { - dotLottiePlayer.loadTheme(""); + dotLottiePlayer.setSlots(""); } });