From b88fc5c59147d84288a4e77de17dbc830dffe259 Mon Sep 17 00:00:00 2001 From: Abdelrahman Ashraf Date: Thu, 27 Jun 2024 19:54:39 +0700 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20replace=20zip=20crat?= =?UTF-8?q?e=20with=20compact=20zip=20reader+deflate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 2 + dotlottie-rs/Cargo.toml | 7 +- dotlottie-rs/src/dotlottie_loader.rs | 429 ++++++++++++++++ dotlottie-rs/src/dotlottie_player.rs | 169 ++++--- dotlottie-rs/src/layout.rs | 4 +- dotlottie-rs/src/lib.rs | 2 + .../src/state_machine/listeners/mod.rs | 477 ++---------------- examples/demo-player/src/main.rs | 1 + examples/demo-state-machine/src/main.rs | 2 +- 9 files changed, 580 insertions(+), 513 deletions(-) create mode 100644 dotlottie-rs/src/dotlottie_loader.rs diff --git a/Makefile b/Makefile index e45db345..2cccbdd9 100644 --- a/Makefile +++ b/Makefile @@ -358,6 +358,8 @@ endef define CARGO_BUILD if [ "$(CARGO_TARGET)" = "wasm32-unknown-emscripten" ]; then \ source $(EMSDK_DIR)/$(EMSDK)_env.sh && \ + export CC="emcc" && \ + export CXX="em++" && \ RUSTFLAGS="-Zlocation-detail=none" cargo +nightly build \ -Z build-std=std,panic_abort \ -Z build-std-features="panic_immediate_abort,optimize_for_size" \ diff --git a/dotlottie-rs/Cargo.toml b/dotlottie-rs/Cargo.toml index fe7d9896..6da4a66d 100644 --- a/dotlottie-rs/Cargo.toml +++ b/dotlottie-rs/Cargo.toml @@ -11,12 +11,11 @@ name = "dotlottie_player_core" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dotlottie_fms = { path = "../dotlottie-fms" } thiserror = "1.0.48" -# "emscripten-no-leading-underscore" branch fix this issue -> https://github.com/sebcrozet/instant/issues/35 instant = {git = "https://github.com/hoodmane/instant", branch = "emscripten-no-leading-underscore", features = ["inaccurate"]} -serde_json = "1.0.107" -serde = { version = "1.0.188", features = ["derive"] } +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +libdeflater = "1.20" [build-dependencies] bindgen = "0.69.1" diff --git a/dotlottie-rs/src/dotlottie_loader.rs b/dotlottie-rs/src/dotlottie_loader.rs new file mode 100644 index 00000000..314914e7 --- /dev/null +++ b/dotlottie-rs/src/dotlottie_loader.rs @@ -0,0 +1,429 @@ +use libdeflater::{DecompressionError, Decompressor}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::from_utf8; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ManifestTheme { + pub id: String, + pub animations: Vec, +} + +#[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, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +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)] +pub enum DotLottieLoaderError { + ArchiveOpenError, + FileFindError { file_name: String }, + AnimationNotFound { animation_id: String }, + AnimationsNotFound, + ManifestNotFound, + InvalidUtf8Error, + InvalidDotLottieFile, + DecompressionError(DecompressionError), + Utf8Error(std::str::Utf8Error), + FromBytesError(std::array::TryFromSliceError), + DataUnavailable, +} + +impl fmt::Display for DotLottieLoaderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DotLottieLoaderError::ArchiveOpenError => write!(f, "Failed to open archive"), + DotLottieLoaderError::FileFindError { file_name } => { + write!(f, "Unable to find the file: {}", file_name) + } + DotLottieLoaderError::AnimationNotFound { animation_id } => { + write!(f, "Animation not found: {}", animation_id) + } + DotLottieLoaderError::AnimationsNotFound => { + write!(f, "No animations found in dotLottie file") + } + DotLottieLoaderError::ManifestNotFound => write!(f, "No manifest found"), + DotLottieLoaderError::InvalidUtf8Error => write!(f, "Invalid UTF-8"), + DotLottieLoaderError::InvalidDotLottieFile => write!(f, "Invalid dotLottie file"), + DotLottieLoaderError::DecompressionError(err) => { + write!(f, "Decompression error: {}", err) + } + DotLottieLoaderError::Utf8Error(err) => write!(f, "UTF-8 error: {}", err), + // DotLottieLoaderError::IOError(err) => write!(f, "IO error: {}", err), + DotLottieLoaderError::FromBytesError(err) => write!(f, "From bytes error: {}", err), + DotLottieLoaderError::DataUnavailable => write!(f, "Data unavailable"), + } + } +} + +impl std::error::Error for DotLottieLoaderError {} + +impl From for DotLottieLoaderError { + fn from(err: DecompressionError) -> Self { + DotLottieLoaderError::DecompressionError(err) + } +} + +impl From for DotLottieLoaderError { + fn from(err: std::str::Utf8Error) -> Self { + DotLottieLoaderError::Utf8Error(err) + } +} + +impl From for DotLottieLoaderError { + fn from(err: std::array::TryFromSliceError) -> Self { + DotLottieLoaderError::FromBytesError(err) + } +} + +fn base64_encode(plain: &[u8]) -> String { + const BASE64_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + plain + .chunks(3) + .flat_map(|chunk| { + let (b1, b2, b3) = match *chunk { + [b1, b2, b3] => (b1, b2, b3), + [b1, b2] => (b1, b2, 0), + [b1] => (b1, 0, 0), + _ => (0, 0, 0), + }; + [ + BASE64_CHARS[(b1 >> 2) as usize], + BASE64_CHARS[((b1 & 0x03) << 4 | (b2 >> 4)) as usize], + if chunk.len() > 1 { + BASE64_CHARS[((b2 & 0x0f) << 2 | (b3 >> 6)) as usize] + } else { + b'=' + }, + if chunk.len() == 3 { + BASE64_CHARS[(b3 & 0x3f) as usize] + } else { + b'=' + }, + ] + }) + .map(|b| b as char) + .collect() +} + +fn inflate(data: &[u8], uncompressed_len: usize) -> Result, DotLottieLoaderError> { + let mut decompressor = Decompressor::new(); + let mut output = vec![0; uncompressed_len]; + + decompressor.deflate_decompress(data, &mut output)?; + + Ok(output) +} + +#[derive(Debug)] +struct LazyFile { + name: String, + + compressed_data: Option>, + + decompressed_data: Option, + decompressed_data_len: usize, +} + +impl LazyFile { + pub fn get_or_decompress_data(&mut self) -> Result<&str, DotLottieLoaderError> { + if self.decompressed_data.is_none() { + if let Some(compressed) = self.compressed_data.take() { + // Optionally take to clear memory + let decompressed_bytes = inflate(&compressed, self.decompressed_data_len)?; + let decompressed_str = from_utf8(&decompressed_bytes) + .map_err(|_| DotLottieLoaderError::InvalidUtf8Error)? + .to_owned(); + self.decompressed_data = Some(decompressed_str); + } else { + // Handle the case where decompression isn't possible due to missing data. + return Err(DotLottieLoaderError::DataUnavailable); + } + } + + Ok(self.decompressed_data.as_deref().unwrap()) + } +} + +pub struct DotLottieLoader { + active_animation_id: String, + manifest: Option, + animations: Vec, + themes: Vec, + images: Vec, + states: Vec, +} + +impl DotLottieLoader { + pub fn new() -> Self { + Self { + active_animation_id: String::new(), + manifest: None, + animations: Vec::new(), + themes: Vec::new(), + images: Vec::new(), + states: Vec::new(), + } + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + let mut file = Self::new(); + file.read(bytes)?; + + Ok(file) + } + + pub fn set_active_animation_id(&mut self, active_animation_id: &str) { + self.active_animation_id = active_animation_id.to_string(); + } + + #[inline] + fn read(&mut self, bytes: &[u8]) -> Result<(), DotLottieLoaderError> { + let eocd_offset = bytes + .len() + .checked_sub(22) + .ok_or(DotLottieLoaderError::InvalidDotLottieFile)?; + + let eocd = &bytes[eocd_offset..]; + if eocd[0..4] != [0x50, 0x4B, 0x05, 0x06] { + return Err(DotLottieLoaderError::InvalidDotLottieFile); + } + + let offset = u32::from_le_bytes(eocd[16..20].try_into()?) as usize; + let num_central_dir = u16::from_le_bytes(eocd[10..12].try_into()?); + + self.process_central_directory(bytes, offset, num_central_dir) + } + + #[inline] + fn process_central_directory( + &mut self, + bytes: &[u8], + offset: usize, + num_central_dir: u16, + ) -> Result<(), DotLottieLoaderError> { + let mut central_dir_offset = offset; + for _ in 0..num_central_dir { + let central_dir_entry = &bytes[central_dir_offset..]; + + // Check if the central directory entry signature is correct + if central_dir_entry[0..4] != [0x50, 0x4B, 0x01, 0x02] { + return Err(DotLottieLoaderError::InvalidDotLottieFile); + } + + let header_offset = u32::from_le_bytes(central_dir_entry[42..46].try_into()?) as usize; + self.process_file_header(bytes, header_offset)?; + + let name_len = u16::from_le_bytes(central_dir_entry[28..30].try_into()?) as usize; + let extra_len = u16::from_le_bytes(central_dir_entry[30..32].try_into()?) as usize; + let comment_len = u16::from_le_bytes(central_dir_entry[32..34].try_into()?) as usize; + central_dir_offset += 46 + name_len + extra_len + comment_len; + } + Ok(()) + } + + #[inline] + fn process_file_header( + &mut self, + bytes: &[u8], + offset: usize, + ) -> Result<(), DotLottieLoaderError> { + let header = &bytes[offset..]; + + if header[0..4] != [0x50, 0x4B, 0x03, 0x04] { + return Err(DotLottieLoaderError::InvalidDotLottieFile); + } + + let name_len = u16::from_le_bytes(header[26..28].try_into()?) as usize; + + let compression_method = u16::from_le_bytes(header[8..10].try_into()?); + let compressed = compression_method != 0; + let data_offset = offset + 30 + name_len; + let data_len = if compressed { + u32::from_le_bytes(header[18..22].try_into()?) as usize + } else { + u32::from_le_bytes(header[22..26].try_into()?) as usize + } as usize; + + let uncompressed_len = u32::from_le_bytes(header[22..26].try_into()?) as usize; + + // panic!(""); + + // Correctly handle the Result from String::from_utf8 before comparing + let file_name_result = String::from_utf8(header[30..30 + name_len].to_vec()) + .map_err(|_| DotLottieLoaderError::InvalidUtf8Error); + let file_data = bytes[data_offset..data_offset + data_len].to_vec(); + + // Now use a match or if let to work with the Result + if let Ok(file_name) = file_name_result { + if file_name == "manifest.json" { + let manifest_data = if compressed { + inflate(&file_data, uncompressed_len)? + } else { + file_data + }; + + let manifest_str = from_utf8(&manifest_data) + .map_err(|_| DotLottieLoaderError::InvalidUtf8Error)?; + + let manifest = serde_json::from_str::(manifest_str); + + self.manifest = + Some(manifest.map_err(|_| DotLottieLoaderError::InvalidDotLottieFile)?); + } else if file_name.starts_with("animations/") && file_name.ends_with(".json") { + if compressed { + self.animations.push(LazyFile { + name: file_name.replace("animations/", "").replace(".json", ""), + compressed_data: Some(file_data), + decompressed_data: None, + decompressed_data_len: uncompressed_len, + }); + } else { + let animation_str = from_utf8(&file_data) + .map_err(|_| DotLottieLoaderError::InvalidUtf8Error)? + .to_string(); + + self.animations.push(LazyFile { + name: file_name.replace("animations/", "").replace(".json", ""), + compressed_data: None, + decompressed_data: Some(animation_str), + decompressed_data_len: uncompressed_len, + }); + } + } else if file_name.starts_with("themes/") && file_name.ends_with(".json") { + if compressed { + self.themes.push(LazyFile { + name: file_name.replace("themes/", "").replace(".json", ""), + compressed_data: Some(file_data), + decompressed_data: None, + decompressed_data_len: uncompressed_len, + }); + } else { + let theme_str = from_utf8(&file_data) + .map_err(|_| DotLottieLoaderError::InvalidUtf8Error)? + .to_string(); + self.themes.push(LazyFile { + name: file_name.replace("themes/", "").replace(".json", ""), + compressed_data: None, + decompressed_data: Some(theme_str), + decompressed_data_len: uncompressed_len, + }); + } + } else if file_name.starts_with("images/") { + if compressed { + self.images.push(LazyFile { + name: file_name.replace("images/", ""), + compressed_data: Some(file_data), + decompressed_data: None, + decompressed_data_len: uncompressed_len, + }); + } else { + self.images.push(LazyFile { + name: file_name.replace("images/", ""), + compressed_data: None, + // base64 encoded image data + decompressed_data: Some(base64_encode(&file_data)), + decompressed_data_len: uncompressed_len, + }); + } + } else if file_name.starts_with("states/") && file_name.ends_with(".json") { + if compressed { + self.states.push(LazyFile { + name: file_name.replace("states/", "").replace(".json", ""), + compressed_data: Some(file_data), + decompressed_data: None, + decompressed_data_len: uncompressed_len, + }); + } else { + let state_machine_str = from_utf8(&file_data) + .map_err(|_| DotLottieLoaderError::InvalidUtf8Error)? + .to_string(); + self.states.push(LazyFile { + name: file_name.replace("states/", "").replace(".json", ""), + compressed_data: None, + decompressed_data: Some(state_machine_str), + decompressed_data_len: uncompressed_len, + }); + } + } + } else { + // Handle or propagate the error as appropriate + return Err(DotLottieLoaderError::InvalidUtf8Error); + } + + Ok(()) + } + + pub fn manifest(&self) -> Option { + self.manifest.clone() + } + + pub fn active_animation_id(&self) -> String { + self.active_animation_id.clone() + } + + pub fn get_animation(&mut self, animation_id: &str) -> Result { + let animation = self + .animations + .iter_mut() + .find(|animation| animation.name == animation_id) + .ok_or(DotLottieLoaderError::AnimationNotFound { + animation_id: animation_id.to_string(), + })?; + + Ok(animation.get_or_decompress_data()?.to_string()) + } + + pub fn get_theme(&mut self, theme_id: &str) -> Result { + let theme = self + .themes + .iter_mut() + .find(|theme| theme.name == theme_id) + .ok_or(DotLottieLoaderError::AnimationNotFound { + animation_id: theme_id.to_string(), + })?; + + Ok(theme.get_or_decompress_data()?.to_string()) + } + + pub fn get_state_machine( + &mut self, + state_machine_id: &str, + ) -> Result { + let state_machine = self + .states + .iter_mut() + .find(|state_machine| state_machine.name == state_machine_id) + .ok_or(DotLottieLoaderError::AnimationNotFound { + animation_id: state_machine_id.to_string(), + })?; + + Ok(state_machine.get_or_decompress_data()?.to_string()) + } +} diff --git a/dotlottie-rs/src/dotlottie_player.rs b/dotlottie-rs/src/dotlottie_player.rs index e803508a..e73e1437 100644 --- a/dotlottie-rs/src/dotlottie_player.rs +++ b/dotlottie-rs/src/dotlottie_player.rs @@ -11,8 +11,7 @@ use crate::{ lottie_renderer::{LottieRenderer, LottieRendererError}, Marker, MarkersMap, StateMachine, }; -use crate::{StateMachineObserver, StateMachineStatus}; -use dotlottie_fms::{DotLottieError, DotLottieManager, Manifest, ManifestAnimation}; +use crate::{DotLottieLoader, Manifest, StateMachineObserver, StateMachineStatus}; pub trait Observer: Send + Sync { fn on_load(&self); @@ -55,7 +54,7 @@ impl Direction { } } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub struct Config { pub mode: Mode, pub loop_animation: bool, @@ -68,22 +67,6 @@ pub struct Config { 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() - } -} - impl Default for Config { fn default() -> Self { Config { @@ -107,7 +90,7 @@ struct DotLottieRuntime { start_time: Instant, loop_count: u32, config: Config, - dotlottie_manager: DotLottieManager, + dotlottie_loader: DotLottieLoader, direction: Direction, markers: MarkersMap, active_animation_id: String, @@ -130,7 +113,7 @@ impl DotLottieRuntime { start_time: Instant::now(), loop_count: 0, config, - dotlottie_manager: DotLottieManager::new(None).unwrap(), + dotlottie_loader: DotLottieLoader::new(), direction, markers: MarkersMap::new(), active_animation_id: String::new(), @@ -252,15 +235,15 @@ impl DotLottieRuntime { } pub fn manifest(&self) -> Option { - self.dotlottie_manager.manifest() + self.dotlottie_loader.manifest() } pub fn size(&self) -> (u32, u32) { (self.renderer.width, self.renderer.height) } - pub fn get_state_machine(&self, state_machine_id: &str) -> Option { - self.dotlottie_manager + pub fn get_state_machine(&mut self, state_machine_id: &str) -> Option { + self.dotlottie_loader .get_state_machine(state_machine_id) .ok() } @@ -654,7 +637,7 @@ impl DotLottieRuntime { self.active_animation_id.clear(); self.active_theme_id.clear(); - self.dotlottie_manager = DotLottieManager::new(None).unwrap(); + self.dotlottie_loader = DotLottieLoader::new(); self.markers = extract_markers(animation_data); @@ -679,16 +662,38 @@ impl DotLottieRuntime { self.active_animation_id.clear(); self.active_theme_id.clear(); - if self.dotlottie_manager.init(file_data).is_err() { - return false; - } + self.dotlottie_loader = { + let loader = DotLottieLoader::from_bytes(file_data); + + match loader { + Ok(loader) => loader, + Err(_) => return false, + } + }; + + let first_animation_id = { + let active_animation_id = self.dotlottie_loader.active_animation_id(); + + if active_animation_id.is_empty() { + self.dotlottie_loader + .manifest() + .and_then(|manifest| { + manifest + .animations + .first() + .map(|animation| animation.id.clone()) + }) + .unwrap_or_default() + } else { + active_animation_id.to_owned() + } + }; - let first_animation: Result = - self.dotlottie_manager.get_active_animation(); + let first_animation = self.dotlottie_loader.get_animation(&first_animation_id); let ok = match first_animation { Ok(animation_data) => { - self.markers = extract_markers(animation_data.as_str()); + self.markers = extract_markers(&animation_data); // For the moment we're ignoring manifest values @@ -703,7 +708,7 @@ impl DotLottieRuntime { }; if ok { - self.active_animation_id = self.dotlottie_manager.active_animation_id(); + self.active_animation_id = first_animation_id; } ok @@ -712,7 +717,7 @@ impl DotLottieRuntime { pub fn load_animation(&mut self, animation_id: &str, width: u32, height: u32) -> bool { self.active_animation_id.clear(); - let animation_data = self.dotlottie_manager.get_animation(animation_id); + let animation_data = self.dotlottie_loader.get_animation(animation_id); let ok = match animation_data { Ok(animation_data) => self.load_animation_common( @@ -730,45 +735,45 @@ impl DotLottieRuntime { ok } - #[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; - } - Err(_error) => return false, - } - - true - } + // #[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; + // } + // Err(_error) => return false, + // } + + // true + // } pub fn resize(&mut self, width: u32, height: u32) -> bool { self.renderer.resize(width, height).is_ok() @@ -818,7 +823,7 @@ impl DotLottieRuntime { is_global_or_active_animation && self - .dotlottie_manager + .dotlottie_loader .get_theme(theme_id) .ok() .and_then(|theme_data| { @@ -1145,10 +1150,14 @@ impl DotLottiePlayerContainer { } pub fn manifest_string(&self) -> String { - self.runtime.try_read().ok() + self.runtime + .try_read() + .ok() .and_then(|runtime| runtime.manifest()) - .map_or_else(String::new, |manifest| manifest.to_string()) - } + .map_or_else(String::new, |manifest| { + serde_json::to_string(&manifest).unwrap_or_else(|_| String::new()) + }) + } pub fn is_complete(&self) -> bool { self.runtime.read().unwrap().is_complete() @@ -1197,8 +1206,8 @@ impl DotLottiePlayerContainer { } pub fn get_state_machine(&self, state_machine_id: &str) -> Option { - match self.runtime.try_read() { - Ok(runtime) => runtime.get_state_machine(state_machine_id), + match self.runtime.try_write() { + Ok(mut runtime) => runtime.get_state_machine(state_machine_id), Err(_) => None, } } @@ -1765,7 +1774,9 @@ impl DotLottiePlayer { } pub fn manifest_string(&self) -> String { - self.player.try_read().map_or_else(|_| String::new(), |player| player.manifest_string()) + self.player + .try_read() + .map_or_else(|_| String::new(), |player| player.manifest_string()) } pub fn is_complete(&self) -> bool { diff --git a/dotlottie-rs/src/layout.rs b/dotlottie-rs/src/layout.rs index 06e1e2bb..36ec1ec3 100644 --- a/dotlottie-rs/src/layout.rs +++ b/dotlottie-rs/src/layout.rs @@ -1,4 +1,4 @@ -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub enum Fit { Contain, Fill, @@ -8,7 +8,7 @@ pub enum Fit { None, } -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Debug)] pub struct Layout { pub fit: Fit, pub align: Vec, diff --git a/dotlottie-rs/src/lib.rs b/dotlottie-rs/src/lib.rs index cc283891..52b22118 100644 --- a/dotlottie-rs/src/lib.rs +++ b/dotlottie-rs/src/lib.rs @@ -1,3 +1,4 @@ +mod dotlottie_loader; mod dotlottie_player; mod layout; mod lottie_renderer; @@ -5,6 +6,7 @@ mod markers; mod state_machine; mod thorvg; +pub use dotlottie_loader::*; pub use dotlottie_player::*; pub use layout::*; pub use lottie_renderer::*; diff --git a/dotlottie-rs/src/state_machine/listeners/mod.rs b/dotlottie-rs/src/state_machine/listeners/mod.rs index b26ff4ea..5aaab942 100644 --- a/dotlottie-rs/src/state_machine/listeners/mod.rs +++ b/dotlottie-rs/src/state_machine/listeners/mod.rs @@ -82,129 +82,22 @@ pub enum Listener { }, } -impl Debug for Listener { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::PointerUp { - r#type, - target, - action, - value, - context_key, - } => f - .debug_struct("PointerUp") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) - .finish(), - Self::PointerDown { - r#type, - target, - action, - value, - context_key, - } => f - .debug_struct("PointerDown") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) - .finish(), - Self::PointerEnter { - r#type, - target, - action, - value, - context_key, - } => f - .debug_struct("PointerEnter") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) - .finish(), - Self::PointerMove { - r#type, - target, - action, - value, - context_key, - } => f - .debug_struct("PointerMove") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) - .finish(), - Self::PointerExit { - r#type, - target, - action, - value, - context_key, - } => f - .debug_struct("PointerExit") - .field("r#type", r#type) - .field("target", target) - .field("action", action) - .field("value", value) - .field("context_key", context_key) - .finish(), - } - } -} - impl ListenerTrait for Listener { fn set_type(&mut self, r#listener_type: ListenerType) { match self { - Listener::PointerUp { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerUp { r#type, .. } => { *r#type = listener_type; } - Listener::PointerDown { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerDown { r#type, .. } => { *r#type = listener_type; } - Listener::PointerEnter { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerEnter { r#type, .. } => { *r#type = listener_type; } - Listener::PointerExit { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerExit { r#type, .. } => { *r#type = listener_type; } - Listener::PointerMove { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerMove { r#type, .. } => { *r#type = listener_type; } } @@ -212,49 +105,19 @@ impl ListenerTrait for Listener { fn set_target(&mut self, new_target: &str) { match self { - Listener::PointerUp { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerUp { target, .. } => { *target = Some(new_target.to_string()); } - Listener::PointerDown { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerDown { target, .. } => { *target = Some(new_target.to_string()); } - Listener::PointerEnter { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerEnter { target, .. } => { *target = Some(new_target.to_string()); } - Listener::PointerExit { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerExit { target, .. } => { *target = Some(new_target.to_string()); } - Listener::PointerMove { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => { + Listener::PointerMove { target, .. } => { *target = Some(new_target.to_string()); } } @@ -262,49 +125,19 @@ impl ListenerTrait for Listener { fn set_action(&mut self, new_action: &str) { match self { - Listener::PointerUp { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { + Listener::PointerUp { action, .. } => { *action = Some(new_action.to_string()); } - Listener::PointerDown { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { + Listener::PointerDown { action, .. } => { *action = Some(new_action.to_string()); } - Listener::PointerEnter { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { + Listener::PointerEnter { action, .. } => { *action = Some(new_action.to_string()); } - Listener::PointerExit { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { + Listener::PointerExit { action, .. } => { *action = Some(new_action.to_string()); } - Listener::PointerMove { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => { + Listener::PointerMove { action, .. } => { *action = Some(new_action.to_string()); } } @@ -312,49 +145,19 @@ impl ListenerTrait for Listener { fn set_value(&mut self, new_value: StringNumberBool) { match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { + Listener::PointerUp { value, .. } => { *value = Some(new_value); } - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { + Listener::PointerDown { value, .. } => { *value = Some(new_value); } - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { + Listener::PointerEnter { value, .. } => { *value = Some(new_value); } - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { + Listener::PointerExit { value, .. } => { *value = Some(new_value); } - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => { + Listener::PointerMove { value, .. } => { *value = Some(new_value); } } @@ -362,49 +165,19 @@ impl ListenerTrait for Listener { fn set_context_key(&mut self, new_context_key: &str) { match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { + Listener::PointerUp { context_key, .. } => { *context_key = Some(new_context_key.to_string()); } - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { + Listener::PointerDown { context_key, .. } => { *context_key = Some(new_context_key.to_string()); } - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { + Listener::PointerEnter { context_key, .. } => { *context_key = Some(new_context_key.to_string()); } - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { + Listener::PointerExit { context_key, .. } => { *context_key = Some(new_context_key.to_string()); } - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => { + Listener::PointerMove { context_key, .. } => { *context_key = Some(new_context_key.to_string()); } } @@ -412,201 +185,51 @@ impl ListenerTrait for Listener { fn get_type(&self) -> &ListenerType { match self { - Listener::PointerUp { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerDown { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerEnter { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerExit { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, - Listener::PointerMove { - r#type, - target: _, - action: _, - value: _, - context_key: _, - } => r#type, + Listener::PointerUp { r#type, .. } => r#type, + Listener::PointerDown { r#type, .. } => r#type, + Listener::PointerEnter { r#type, .. } => r#type, + Listener::PointerExit { r#type, .. } => r#type, + Listener::PointerMove { r#type, .. } => r#type, } } fn get_target(&self) -> Option { match self { - Listener::PointerUp { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerDown { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerEnter { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerExit { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), - Listener::PointerMove { - r#type: _, - target, - action: _, - value: _, - context_key: _, - } => target.clone(), + Listener::PointerUp { target, .. } => target.clone(), + Listener::PointerDown { target, .. } => target.clone(), + Listener::PointerEnter { target, .. } => target.clone(), + Listener::PointerExit { target, .. } => target.clone(), + Listener::PointerMove { target, .. } => target.clone(), } } fn get_action(&self) -> Option { match self { - Listener::PointerUp { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerDown { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerEnter { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerExit { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), - Listener::PointerMove { - r#type: _, - target: _, - action, - value: _, - context_key: _, - } => action.clone(), + Listener::PointerUp { action, .. } => action.clone(), + Listener::PointerDown { action, .. } => action.clone(), + Listener::PointerEnter { action, .. } => action.clone(), + Listener::PointerExit { action, .. } => action.clone(), + Listener::PointerMove { action, .. } => action.clone(), } } fn get_value(&self) -> Option<&StringNumberBool> { match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value, - context_key: _, - } => value.as_ref(), + Listener::PointerUp { value, .. } => value.as_ref(), + Listener::PointerDown { value, .. } => value.as_ref(), + Listener::PointerEnter { value, .. } => value.as_ref(), + Listener::PointerExit { value, .. } => value.as_ref(), + Listener::PointerMove { value, .. } => value.as_ref(), } } fn get_context_key(&self) -> Option { match self { - Listener::PointerUp { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerDown { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerEnter { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerExit { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), - Listener::PointerMove { - r#type: _, - target: _, - action: _, - value: _, - context_key, - } => context_key.clone(), + Listener::PointerUp { context_key, .. } => context_key.clone(), + Listener::PointerDown { context_key, .. } => context_key.clone(), + Listener::PointerEnter { context_key, .. } => context_key.clone(), + Listener::PointerExit { context_key, .. } => context_key.clone(), + Listener::PointerMove { context_key, .. } => context_key.clone(), } } } diff --git a/examples/demo-player/src/main.rs b/examples/demo-player/src/main.rs index cf3fc82f..47e5d6fe 100644 --- a/examples/demo-player/src/main.rs +++ b/examples/demo-player/src/main.rs @@ -118,6 +118,7 @@ fn main() { path.push("src/markers.json"); let mut lottie_player: DotLottiePlayer = DotLottiePlayer::new(Config { + autoplay: true, loop_animation: true, background_color: 0xffffffff, layout: Layout::new(dotlottie_player_core::Fit::None, vec![1.0, 0.5]), diff --git a/examples/demo-state-machine/src/main.rs b/examples/demo-state-machine/src/main.rs index e4ac15d5..c96bb877 100644 --- a/examples/demo-state-machine/src/main.rs +++ b/examples/demo-state-machine/src/main.rs @@ -218,7 +218,7 @@ fn main() { pushed -= 1.0; let p = &mut *locked_player.write().unwrap(); - p.tmp_set_state_machine_context("counter_0", pushed); + p.set_state_machine_numeric_context("counter_0", pushed); p.post_event(&string_event); }