move rust code to own library for targeting
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
multiple environments like wasm, tauri and more put non wasm compatible features like rayon multithreading behind feature flags
This commit is contained in:
parent
c352ba7fd3
commit
6d2ffe6902
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -10,8 +10,6 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
env:
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: app
|
working-directory: app
|
||||||
|
1
lib/creator_rs/.gitignore
vendored
Normal file
1
lib/creator_rs/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
target
|
3834
lib/creator_rs/Cargo.lock
generated
Normal file
3834
lib/creator_rs/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
lib/creator_rs/Cargo.toml
Normal file
31
lib/creator_rs/Cargo.toml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
[package]
|
||||||
|
name = "creator_rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Enrico Bühler <buehler@unom.io>"]
|
||||||
|
description = "Motion Design toolkit with spring, eased and linear based value interpolation, timeline functionality and more"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/tempblade/creator"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rayon = { version = "1.7", optional = true }
|
||||||
|
font-kit = { version = "0.11", optional = true }
|
||||||
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
simple-easing = "1.0"
|
||||||
|
tauri = { version = "1.4", optional = true, features = [
|
||||||
|
"dialog-open",
|
||||||
|
"dialog-save",
|
||||||
|
"shell-open",
|
||||||
|
] }
|
||||||
|
uuid = { version = "1.3", features = ["v4", "macro-diagnostics", "js"] }
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
tauri = ["dep:tauri"]
|
||||||
|
parallelization = ["dep:rayon"]
|
||||||
|
fonts = ["dep:font-kit"]
|
2
lib/creator_rs/src/animation/mod.rs
Normal file
2
lib/creator_rs/src/animation/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod primitives;
|
||||||
|
pub mod timeline;
|
36
lib/creator_rs/src/animation/primitives/circular_text.rs
Normal file
36
lib/creator_rs/src/animation/primitives/circular_text.rs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
impl Animateable for AnimatedCircularText<'_> {
|
||||||
|
fn draw(&mut self, mut canvas: &mut Canvas, timeline: &Timeline<'_>) {
|
||||||
|
self.prepare(&mut canvas, &self.animation_data);
|
||||||
|
|
||||||
|
self.sort_keyframes();
|
||||||
|
|
||||||
|
self.paint.set_anti_alias(true);
|
||||||
|
|
||||||
|
let default_text_typeface = &Typeface::default();
|
||||||
|
|
||||||
|
let default_text_font = &Font::from_typeface(default_text_typeface, 190.0);
|
||||||
|
|
||||||
|
let text_font: &Font = match self.font {
|
||||||
|
Some(font) => font,
|
||||||
|
None => default_text_font,
|
||||||
|
};
|
||||||
|
|
||||||
|
let radius: f32 = 0.35 * timeline.size.0.min(timeline.size.1) as f32;
|
||||||
|
|
||||||
|
let mut path = Path::new();
|
||||||
|
|
||||||
|
path.add_circle(
|
||||||
|
(timeline.size.0 / 2, timeline.size.1 / 2),
|
||||||
|
radius,
|
||||||
|
PathDirection::CW,
|
||||||
|
);
|
||||||
|
|
||||||
|
let text_width = text_font.measure_str(self.text, Some(&self.paint));
|
||||||
|
|
||||||
|
canvas.draw
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
self.origin.sort_keyframes();
|
||||||
|
}
|
||||||
|
}
|
78
lib/creator_rs/src/animation/primitives/entities/common.rs
Normal file
78
lib/creator_rs/src/animation/primitives/entities/common.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::animation::{primitives::utils::timestamp_to_frame, timeline::Timeline};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
ellipse::{AnimatedEllipseEntity, EllipseEntity},
|
||||||
|
rect::{AnimatedRectEntity, RectEntity},
|
||||||
|
staggered_text::{AnimatedStaggeredTextEntity, StaggeredTextEntity},
|
||||||
|
text::{AnimatedTextEntity, TextEntity},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Drawable {
|
||||||
|
fn should_draw(&self, animation_data: &AnimationData, timeline: &Timeline) -> bool {
|
||||||
|
let start_frame = timestamp_to_frame(animation_data.offset, timeline.fps);
|
||||||
|
let end_frame = timestamp_to_frame(
|
||||||
|
animation_data.offset + animation_data.duration,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
// println!("start {0} end {1}", start_frame, end_frame);
|
||||||
|
|
||||||
|
let is_before = timeline.render_state.curr_frame < start_frame;
|
||||||
|
let is_after = timeline.render_state.curr_frame > end_frame;
|
||||||
|
let is_between = !is_after && !is_before;
|
||||||
|
|
||||||
|
if is_between {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Animateable {
|
||||||
|
fn sort_keyframes(&mut self);
|
||||||
|
|
||||||
|
fn calculate(&mut self, timeline: &Timeline) -> Option<Entity>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct Cache {
|
||||||
|
pub valid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum AnimatedEntity {
|
||||||
|
Text(AnimatedTextEntity),
|
||||||
|
StaggeredText(AnimatedStaggeredTextEntity),
|
||||||
|
Ellipse(AnimatedEllipseEntity),
|
||||||
|
Rect(AnimatedRectEntity),
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Entity {
|
||||||
|
Text(TextEntity),
|
||||||
|
StaggeredText(StaggeredTextEntity),
|
||||||
|
Ellipse(EllipseEntity),
|
||||||
|
Rect(RectEntity),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedEntity {
|
||||||
|
pub fn calculate(&mut self, timeline: &Timeline) -> Option<Entity> {
|
||||||
|
match self {
|
||||||
|
Self::Text(text_entity) => text_entity.calculate(timeline),
|
||||||
|
Self::Rect(box_entity) => box_entity.calculate(timeline),
|
||||||
|
Self::StaggeredText(staggered_text_entity) => staggered_text_entity.calculate(timeline),
|
||||||
|
Self::Ellipse(ellipse_entity) => ellipse_entity.calculate(timeline),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AnimationData {
|
||||||
|
pub offset: f32,
|
||||||
|
pub duration: f32,
|
||||||
|
pub visible: bool,
|
||||||
|
}
|
92
lib/creator_rs/src/animation/primitives/entities/ellipse.rs
Normal file
92
lib/creator_rs/src/animation/primitives/entities/ellipse.rs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::animation::{
|
||||||
|
primitives::{
|
||||||
|
paint::Paint,
|
||||||
|
transform::{AnimatedTransform, Transform},
|
||||||
|
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||||
|
},
|
||||||
|
timeline::Timeline,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::common::{Animateable, AnimationData, Cache, Drawable, Entity};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AnimatedEllipseEntity {
|
||||||
|
pub paint: Paint,
|
||||||
|
pub id: String,
|
||||||
|
pub cache: Cache,
|
||||||
|
pub radius: AnimatedFloatVec2,
|
||||||
|
pub origin: AnimatedFloatVec2,
|
||||||
|
pub position: AnimatedFloatVec2,
|
||||||
|
pub animation_data: AnimationData,
|
||||||
|
pub transform: Option<AnimatedTransform>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct EllipseEntity {
|
||||||
|
pub radius: (f32, f32),
|
||||||
|
pub cache: Cache,
|
||||||
|
pub id: String,
|
||||||
|
pub position: (f32, f32),
|
||||||
|
pub origin: (f32, f32),
|
||||||
|
pub paint: Paint,
|
||||||
|
pub transform: Option<Transform>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawable for AnimatedEllipseEntity {}
|
||||||
|
impl Animateable for AnimatedEllipseEntity {
|
||||||
|
fn calculate(&mut self, timeline: &Timeline) -> Option<Entity> {
|
||||||
|
let should_draw = self.should_draw(&self.animation_data, timeline);
|
||||||
|
|
||||||
|
if should_draw {
|
||||||
|
self.sort_keyframes();
|
||||||
|
|
||||||
|
let radius = self.radius.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let position = self.position.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let origin = self.origin.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let transform: Option<Transform> = match self.transform.clone() {
|
||||||
|
Some(mut val) => Some(val.calculate(timeline, &self.animation_data)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Entity::Ellipse(EllipseEntity {
|
||||||
|
id: self.id.clone(),
|
||||||
|
radius,
|
||||||
|
position,
|
||||||
|
origin,
|
||||||
|
cache: self.cache.clone(),
|
||||||
|
paint: self.paint.clone(),
|
||||||
|
transform,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
let transform = self.transform.clone();
|
||||||
|
|
||||||
|
if let Some(mut transform) = transform {
|
||||||
|
transform.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.position.sort_keyframes();
|
||||||
|
self.radius.sort_keyframes();
|
||||||
|
}
|
||||||
|
}
|
5
lib/creator_rs/src/animation/primitives/entities/mod.rs
Normal file
5
lib/creator_rs/src/animation/primitives/entities/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pub mod common;
|
||||||
|
pub mod ellipse;
|
||||||
|
pub mod rect;
|
||||||
|
pub mod staggered_text;
|
||||||
|
pub mod text;
|
90
lib/creator_rs/src/animation/primitives/entities/rect.rs
Normal file
90
lib/creator_rs/src/animation/primitives/entities/rect.rs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::animation::{
|
||||||
|
primitives::{
|
||||||
|
paint::Paint,
|
||||||
|
transform::{AnimatedTransform, Transform},
|
||||||
|
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||||
|
},
|
||||||
|
timeline::Timeline,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::common::{Animateable, AnimationData, Cache, Drawable, Entity};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AnimatedRectEntity {
|
||||||
|
pub id: String,
|
||||||
|
pub cache: Cache,
|
||||||
|
pub position: AnimatedFloatVec2,
|
||||||
|
pub size: AnimatedFloatVec2,
|
||||||
|
pub origin: AnimatedFloatVec2,
|
||||||
|
pub paint: Paint,
|
||||||
|
pub animation_data: AnimationData,
|
||||||
|
pub transform: Option<AnimatedTransform>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct RectEntity {
|
||||||
|
pub id: String,
|
||||||
|
pub cache: Cache,
|
||||||
|
pub position: (f32, f32),
|
||||||
|
pub size: (f32, f32),
|
||||||
|
pub origin: (f32, f32),
|
||||||
|
pub paint: Paint,
|
||||||
|
pub transform: Option<Transform>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawable for AnimatedRectEntity {}
|
||||||
|
impl Animateable for AnimatedRectEntity {
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
if let Some(x) = &mut self.transform {
|
||||||
|
x.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.position.sort_keyframes();
|
||||||
|
self.size.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate(&mut self, timeline: &Timeline) -> Option<Entity> {
|
||||||
|
let should_draw = self.should_draw(&self.animation_data, timeline);
|
||||||
|
|
||||||
|
if should_draw {
|
||||||
|
self.sort_keyframes();
|
||||||
|
|
||||||
|
let position = self.position.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let size = self.size.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let origin = self.origin.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let transform: Option<Transform> = match self.transform.clone() {
|
||||||
|
Some(mut val) => Some(val.calculate(timeline, &self.animation_data)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Entity::Rect(RectEntity {
|
||||||
|
id: self.id.clone(),
|
||||||
|
cache: self.cache.clone(),
|
||||||
|
position,
|
||||||
|
size,
|
||||||
|
origin,
|
||||||
|
paint: self.paint.clone(),
|
||||||
|
transform,
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,112 @@
|
|||||||
|
use super::common::{Animateable, AnimationData, Cache, Drawable, Entity};
|
||||||
|
use crate::animation::{
|
||||||
|
primitives::{
|
||||||
|
paint::TextPaint,
|
||||||
|
transform::{AnimatedTransform, Transform},
|
||||||
|
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||||
|
},
|
||||||
|
timeline::Timeline,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AnimatedStaggeredTextLetter {
|
||||||
|
pub transform: Option<AnimatedTransform>,
|
||||||
|
pub paint: TextPaint,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StaggeredTextLetter {
|
||||||
|
pub transform: Option<Vec<Transform>>,
|
||||||
|
pub paint: TextPaint,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AnimatedStaggeredTextEntity {
|
||||||
|
pub id: String,
|
||||||
|
pub cache: Cache,
|
||||||
|
pub text: String,
|
||||||
|
pub stagger: f32,
|
||||||
|
pub origin: AnimatedFloatVec2,
|
||||||
|
pub animation_data: AnimationData,
|
||||||
|
pub transform: Option<AnimatedTransform>,
|
||||||
|
pub letter: AnimatedStaggeredTextLetter,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct StaggeredTextEntity {
|
||||||
|
pub id: String,
|
||||||
|
pub cache: Cache,
|
||||||
|
pub text: String,
|
||||||
|
pub stagger: f32,
|
||||||
|
pub origin: (f32, f32),
|
||||||
|
pub transform: Option<Transform>,
|
||||||
|
pub animation_data: AnimationData,
|
||||||
|
pub letter: StaggeredTextLetter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawable for AnimatedStaggeredTextEntity {}
|
||||||
|
impl Animateable for AnimatedStaggeredTextEntity {
|
||||||
|
fn calculate(&mut self, timeline: &Timeline) -> Option<Entity> {
|
||||||
|
let should_draw: bool = self.should_draw(&self.animation_data, timeline);
|
||||||
|
|
||||||
|
if should_draw {
|
||||||
|
self.sort_keyframes();
|
||||||
|
|
||||||
|
let transform: Option<Transform> = match self.transform.clone() {
|
||||||
|
Some(mut val) => Some(val.calculate(timeline, &self.animation_data)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate over the chars of the string and calculate the animation with the staggered offset
|
||||||
|
let letter_transform: Option<Vec<Transform>> = match self.letter.transform.clone() {
|
||||||
|
Some(mut val) => {
|
||||||
|
let mut transforms: Vec<Transform> = Vec::new();
|
||||||
|
|
||||||
|
for c in self.text.chars().enumerate() {
|
||||||
|
let mut animation_data = self.animation_data.clone();
|
||||||
|
animation_data.offset += self.stagger * c.0 as f32;
|
||||||
|
|
||||||
|
let transform = val.calculate(timeline, &animation_data);
|
||||||
|
transforms.push(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(transforms)
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let origin = self.origin.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(Entity::StaggeredText(StaggeredTextEntity {
|
||||||
|
id: self.id.clone(),
|
||||||
|
transform,
|
||||||
|
cache: self.cache.clone(),
|
||||||
|
stagger: self.stagger,
|
||||||
|
origin,
|
||||||
|
text: self.text.clone(),
|
||||||
|
animation_data: self.animation_data.clone(),
|
||||||
|
letter: StaggeredTextLetter {
|
||||||
|
transform: letter_transform,
|
||||||
|
paint: self.letter.paint.clone(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
if let Some(x) = &mut self.transform {
|
||||||
|
x.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(x) = &mut self.letter.transform {
|
||||||
|
x.sort_keyframes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
82
lib/creator_rs/src/animation/primitives/entities/text.rs
Normal file
82
lib/creator_rs/src/animation/primitives/entities/text.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::animation::{
|
||||||
|
primitives::{
|
||||||
|
paint::TextPaint,
|
||||||
|
transform::{AnimatedTransform, Transform},
|
||||||
|
values::animated_values::{AnimatedFloatVec2, AnimatedValue},
|
||||||
|
},
|
||||||
|
timeline::Timeline,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::common::{Animateable, AnimationData, Cache, Drawable, Entity};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct TextEntity {
|
||||||
|
pub id: String,
|
||||||
|
pub cache: Cache,
|
||||||
|
pub text: String,
|
||||||
|
pub origin: (f32, f32),
|
||||||
|
pub paint: TextPaint,
|
||||||
|
pub transform: Option<Transform>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct AnimatedTextEntity {
|
||||||
|
pub id: String,
|
||||||
|
pub cache: Cache,
|
||||||
|
pub text: String,
|
||||||
|
pub origin: AnimatedFloatVec2,
|
||||||
|
pub paint: TextPaint,
|
||||||
|
pub animation_data: AnimationData,
|
||||||
|
pub transform: Option<AnimatedTransform>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drawable for AnimatedTextEntity {}
|
||||||
|
|
||||||
|
impl AnimatedTextEntity {
|
||||||
|
fn into_static(&mut self, timeline: &Timeline) -> TextEntity {
|
||||||
|
self.sort_keyframes();
|
||||||
|
|
||||||
|
let origin = self.origin.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
&self.animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let transform: Option<Transform> = match self.transform.clone() {
|
||||||
|
Some(mut val) => Some(val.calculate(timeline, &self.animation_data)),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
TextEntity {
|
||||||
|
id: self.id.clone(),
|
||||||
|
cache: self.cache.clone(),
|
||||||
|
transform,
|
||||||
|
text: self.text.clone(),
|
||||||
|
origin,
|
||||||
|
paint: self.paint.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Animateable for AnimatedTextEntity {
|
||||||
|
fn calculate(&mut self, timeline: &Timeline) -> Option<Entity> {
|
||||||
|
let should_draw = self.should_draw(&self.animation_data, timeline);
|
||||||
|
|
||||||
|
if should_draw {
|
||||||
|
self.sort_keyframes();
|
||||||
|
|
||||||
|
Some(Entity::Text(self.into_static(timeline)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
if let Some(x) = &mut self.transform {
|
||||||
|
x.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.origin.sort_keyframes();
|
||||||
|
}
|
||||||
|
}
|
170
lib/creator_rs/src/animation/primitives/interpolations.rs
Normal file
170
lib/creator_rs/src/animation/primitives/interpolations.rs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use super::keyframe::RenderedKeyframe;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use simple_easing::{
|
||||||
|
circ_in, circ_in_out, circ_out, cubic_in, cubic_in_out, cubic_out, expo_in, expo_in_out,
|
||||||
|
expo_out, quad_in, quad_in_out, quad_out, quart_in, quart_in_out, quart_out, quint_in,
|
||||||
|
quint_in_out, quint_out,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct SpringProperties {
|
||||||
|
pub mass: f32,
|
||||||
|
pub damping: f32,
|
||||||
|
pub stiffness: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct SpringState {
|
||||||
|
pub velocity: f32,
|
||||||
|
pub last_val: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||||
|
#[serde(tag = "easing_function")]
|
||||||
|
pub enum EasingFunction {
|
||||||
|
QuintOut,
|
||||||
|
QuintIn,
|
||||||
|
QuintInOut,
|
||||||
|
CircOut,
|
||||||
|
CircIn,
|
||||||
|
CircInOut,
|
||||||
|
CubicOut,
|
||||||
|
CubicIn,
|
||||||
|
CubicInOut,
|
||||||
|
ExpoOut,
|
||||||
|
ExpoIn,
|
||||||
|
ExpoInOut,
|
||||||
|
QuadOut,
|
||||||
|
QuadIn,
|
||||||
|
QuadInOut,
|
||||||
|
QuartOut,
|
||||||
|
QuartIn,
|
||||||
|
QuartInOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EasingFunction {
|
||||||
|
fn ease(self: &Self, t: f32) -> f32 {
|
||||||
|
match self {
|
||||||
|
EasingFunction::QuintOut => quint_out(t),
|
||||||
|
EasingFunction::QuintIn => quint_in(t),
|
||||||
|
EasingFunction::QuintInOut => quint_in_out(t),
|
||||||
|
EasingFunction::CircOut => circ_out(t),
|
||||||
|
EasingFunction::CircIn => circ_in(t),
|
||||||
|
EasingFunction::CircInOut => circ_in_out(t),
|
||||||
|
EasingFunction::CubicOut => cubic_out(t),
|
||||||
|
EasingFunction::CubicIn => cubic_in(t),
|
||||||
|
EasingFunction::CubicInOut => cubic_in_out(t),
|
||||||
|
EasingFunction::ExpoOut => expo_out(t),
|
||||||
|
EasingFunction::ExpoIn => expo_in(t),
|
||||||
|
EasingFunction::ExpoInOut => expo_in_out(t),
|
||||||
|
EasingFunction::QuadOut => quad_out(t),
|
||||||
|
EasingFunction::QuadIn => quad_in(t),
|
||||||
|
EasingFunction::QuadInOut => quad_in_out(t),
|
||||||
|
EasingFunction::QuartOut => quart_out(t),
|
||||||
|
EasingFunction::QuartIn => quart_in(t),
|
||||||
|
EasingFunction::QuartInOut => quart_in_out(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum InterpolationType {
|
||||||
|
Linear,
|
||||||
|
Spring(SpringProperties),
|
||||||
|
EasingFunction(EasingFunction),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_spring_value(
|
||||||
|
curr_frame: i32,
|
||||||
|
start_value: f32,
|
||||||
|
target_value: f32,
|
||||||
|
start_frame: i32,
|
||||||
|
_end_frame: i32,
|
||||||
|
spring_props: &SpringProperties,
|
||||||
|
) -> f32 {
|
||||||
|
const PRECISION: f32 = 0.01;
|
||||||
|
const STEP: f32 = 10.0;
|
||||||
|
const REST_VELOCITY: f32 = PRECISION / 10.0;
|
||||||
|
|
||||||
|
let _is_growing = match start_value.total_cmp(&target_value) {
|
||||||
|
Ordering::Equal => false,
|
||||||
|
Ordering::Less => false,
|
||||||
|
Ordering::Greater => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut _is_moving = false;
|
||||||
|
let mut spring_state = SpringState {
|
||||||
|
last_val: start_value,
|
||||||
|
velocity: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut position = start_value;
|
||||||
|
let relative_curr_frame = curr_frame - start_frame;
|
||||||
|
|
||||||
|
// println!("target_value {target_value} start_value {start_value}");
|
||||||
|
// println!("start_frame {start_frame} end_frame {end_frame}");
|
||||||
|
|
||||||
|
for _ in 0..relative_curr_frame {
|
||||||
|
let _is_moving = spring_state.velocity.abs() > REST_VELOCITY;
|
||||||
|
|
||||||
|
let spring_force = -spring_props.stiffness * 0.000001 * (position - target_value);
|
||||||
|
let damping_force = -spring_props.damping * 0.001 * spring_state.velocity;
|
||||||
|
let acceleration = (spring_force + damping_force) / spring_props.mass; // pt/ms^2
|
||||||
|
|
||||||
|
spring_state.velocity = spring_state.velocity + acceleration * STEP; // pt/ms
|
||||||
|
position = position + spring_state.velocity * STEP;
|
||||||
|
// println!("{position}")
|
||||||
|
}
|
||||||
|
|
||||||
|
position
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpolate_rendered_keyframes(
|
||||||
|
first_ren_keyframe: &RenderedKeyframe,
|
||||||
|
second_ren_keyframe: &RenderedKeyframe,
|
||||||
|
curr_frame: i32,
|
||||||
|
interpolation_type: InterpolationType,
|
||||||
|
_fps: i16,
|
||||||
|
) -> f32 {
|
||||||
|
let frame_range = second_ren_keyframe.absolute_frame - first_ren_keyframe.absolute_frame;
|
||||||
|
let position_in_range = curr_frame - first_ren_keyframe.absolute_frame;
|
||||||
|
let progress: f32 = (1.0 / frame_range as f32) * position_in_range as f32;
|
||||||
|
|
||||||
|
/* println!(
|
||||||
|
"Progress:{0} Frame_Range: {1} Position_In_Range: {2}",
|
||||||
|
progress, frame_range, position_in_range
|
||||||
|
); */
|
||||||
|
|
||||||
|
let value_diff = second_ren_keyframe.keyframe.value - first_ren_keyframe.keyframe.value;
|
||||||
|
|
||||||
|
match interpolation_type {
|
||||||
|
InterpolationType::Linear => {
|
||||||
|
let interpolated_val =
|
||||||
|
first_ren_keyframe.keyframe.value + (value_diff * progress as f32);
|
||||||
|
|
||||||
|
return interpolated_val;
|
||||||
|
}
|
||||||
|
InterpolationType::EasingFunction(easing_function) => {
|
||||||
|
let eased_progress = easing_function.ease(progress);
|
||||||
|
|
||||||
|
let interpolated_val =
|
||||||
|
first_ren_keyframe.keyframe.value + (value_diff * eased_progress as f32);
|
||||||
|
|
||||||
|
return interpolated_val;
|
||||||
|
}
|
||||||
|
InterpolationType::Spring(spring_properties) => {
|
||||||
|
let interpolated_value = calculate_spring_value(
|
||||||
|
curr_frame,
|
||||||
|
first_ren_keyframe.keyframe.value,
|
||||||
|
second_ren_keyframe.keyframe.value,
|
||||||
|
first_ren_keyframe.absolute_frame,
|
||||||
|
second_ren_keyframe.absolute_frame,
|
||||||
|
&spring_properties,
|
||||||
|
);
|
||||||
|
return interpolated_value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
158
lib/creator_rs/src/animation/primitives/keyframe.rs
Normal file
158
lib/creator_rs/src/animation/primitives/keyframe.rs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
use std::{cmp::Ordering, sync::Arc};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
entities::common::AnimationData,
|
||||||
|
interpolations::{interpolate_rendered_keyframes, InterpolationType},
|
||||||
|
utils::render_keyframe,
|
||||||
|
values::values::Float,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct Keyframe {
|
||||||
|
pub value: Float,
|
||||||
|
pub offset: f32,
|
||||||
|
pub id: Arc<str>,
|
||||||
|
pub interpolation: Option<InterpolationType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keyframe {
|
||||||
|
pub fn new(
|
||||||
|
value: f32,
|
||||||
|
offset: f32,
|
||||||
|
id: Arc<str>,
|
||||||
|
interpolation: Option<InterpolationType>,
|
||||||
|
) -> Self {
|
||||||
|
Keyframe {
|
||||||
|
value,
|
||||||
|
offset,
|
||||||
|
id,
|
||||||
|
interpolation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RenderedKeyframe {
|
||||||
|
pub absolute_frame: i32,
|
||||||
|
pub keyframe: Keyframe,
|
||||||
|
pub index: usize,
|
||||||
|
pub distance_from_curr: i32,
|
||||||
|
pub abs_distance_from_curr: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct Keyframes {
|
||||||
|
pub values: Vec<Keyframe>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keyframes {
|
||||||
|
pub fn get_value_at_frame(
|
||||||
|
&self,
|
||||||
|
curr_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> f32 {
|
||||||
|
let keyframe_count = self.values.len();
|
||||||
|
|
||||||
|
if keyframe_count > 0 {
|
||||||
|
let mut rendered_keyframes: Vec<RenderedKeyframe> = self
|
||||||
|
.values
|
||||||
|
.to_vec()
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, keyframe)| {
|
||||||
|
render_keyframe(keyframe, animation_data, index, curr_frame, fps)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
rendered_keyframes
|
||||||
|
.sort_by(|a, b| a.abs_distance_from_curr.cmp(&b.abs_distance_from_curr));
|
||||||
|
|
||||||
|
let closest_keyframe = rendered_keyframes.get(0).unwrap();
|
||||||
|
|
||||||
|
let result = match (closest_keyframe.distance_from_curr).cmp(&0) {
|
||||||
|
Ordering::Equal => closest_keyframe.keyframe.value,
|
||||||
|
Ordering::Greater => {
|
||||||
|
if closest_keyframe.absolute_frame == curr_frame {
|
||||||
|
return closest_keyframe.keyframe.value;
|
||||||
|
} else {
|
||||||
|
let previous_keyframe =
|
||||||
|
rendered_keyframes.to_vec().into_iter().find(|keyframe| {
|
||||||
|
if closest_keyframe.index > 0 {
|
||||||
|
keyframe.index == closest_keyframe.index - 1
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(previous_keyframe) = previous_keyframe {
|
||||||
|
let interpolation = match previous_keyframe.keyframe.interpolation {
|
||||||
|
Some(val) => val,
|
||||||
|
None => InterpolationType::Linear,
|
||||||
|
};
|
||||||
|
|
||||||
|
let interpolated_value = interpolate_rendered_keyframes(
|
||||||
|
&previous_keyframe,
|
||||||
|
closest_keyframe,
|
||||||
|
curr_frame,
|
||||||
|
interpolation,
|
||||||
|
fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
return interpolated_value;
|
||||||
|
} else {
|
||||||
|
if closest_keyframe.absolute_frame > curr_frame {
|
||||||
|
return closest_keyframe.keyframe.value;
|
||||||
|
} else {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ordering::Less => {
|
||||||
|
if closest_keyframe.absolute_frame == curr_frame {
|
||||||
|
return closest_keyframe.keyframe.value;
|
||||||
|
} else {
|
||||||
|
let next_keyframe = rendered_keyframes
|
||||||
|
.to_vec()
|
||||||
|
.into_iter()
|
||||||
|
.find(|keyframe| keyframe.index == closest_keyframe.index + 1);
|
||||||
|
|
||||||
|
if let Some(next_keyframe) = next_keyframe {
|
||||||
|
let interpolation = match closest_keyframe.keyframe.interpolation {
|
||||||
|
Some(val) => val,
|
||||||
|
None => InterpolationType::Linear,
|
||||||
|
};
|
||||||
|
|
||||||
|
let interpolated_value = interpolate_rendered_keyframes(
|
||||||
|
closest_keyframe,
|
||||||
|
&next_keyframe,
|
||||||
|
curr_frame,
|
||||||
|
interpolation,
|
||||||
|
fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
return interpolated_value;
|
||||||
|
} else {
|
||||||
|
if closest_keyframe.absolute_frame < curr_frame {
|
||||||
|
return closest_keyframe.keyframe.value;
|
||||||
|
} else {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sort(&mut self) {
|
||||||
|
self.values.sort_by(|a, b| a.offset.total_cmp(&b.offset));
|
||||||
|
}
|
||||||
|
}
|
8
lib/creator_rs/src/animation/primitives/mod.rs
Normal file
8
lib/creator_rs/src/animation/primitives/mod.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
pub mod entities;
|
||||||
|
pub mod interpolations;
|
||||||
|
pub mod keyframe;
|
||||||
|
pub mod paint;
|
||||||
|
pub mod tests;
|
||||||
|
pub mod transform;
|
||||||
|
pub mod utils;
|
||||||
|
pub mod values;
|
70
lib/creator_rs/src/animation/primitives/paint.rs
Normal file
70
lib/creator_rs/src/animation/primitives/paint.rs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Color {
|
||||||
|
value: (u8, u8, u8, f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
pub fn new(red: u8, green: u8, blue: u8, alpha: f32) -> Color {
|
||||||
|
Color {
|
||||||
|
value: (red, green, blue, alpha),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum PaintStyle {
|
||||||
|
Fill(FillStyle),
|
||||||
|
Stroke(StrokeStyle),
|
||||||
|
StrokeAndFill(StrokeAndFillStyle),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Paint {
|
||||||
|
pub style: PaintStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct TextPaint {
|
||||||
|
pub style: PaintStyle,
|
||||||
|
pub align: TextAlign,
|
||||||
|
pub font_name: String,
|
||||||
|
pub size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct StrokeStyle {
|
||||||
|
pub color: Color,
|
||||||
|
pub width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct StrokeAndFillStyle {
|
||||||
|
pub stroke: StrokeStyle,
|
||||||
|
pub fill: FillStyle,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct FillStyle {
|
||||||
|
pub color: Color,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum TextAlign {
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct FontDefinition {
|
||||||
|
pub family_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Font {
|
||||||
|
pub glyph_count: i32,
|
||||||
|
pub weight: i32,
|
||||||
|
pub style: String,
|
||||||
|
}
|
183
lib/creator_rs/src/animation/primitives/tests.rs
Normal file
183
lib/creator_rs/src/animation/primitives/tests.rs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
use crate::animation::primitives::{
|
||||||
|
entities::common::AnimationData,
|
||||||
|
interpolations::{calculate_spring_value, SpringProperties},
|
||||||
|
keyframe::{Keyframe, Keyframes},
|
||||||
|
utils::timestamp_to_frame,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn interpolates_the_input() {
|
||||||
|
use crate::animation::primitives::{
|
||||||
|
interpolations::{interpolate_rendered_keyframes, InterpolationType},
|
||||||
|
keyframe::{Keyframe, Keyframes, RenderedKeyframe},
|
||||||
|
utils::render_keyframe,
|
||||||
|
};
|
||||||
|
|
||||||
|
let animation_data = AnimationData {
|
||||||
|
offset: 0.0,
|
||||||
|
duration: 3.0,
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fps = 60;
|
||||||
|
|
||||||
|
let keyframes1 = Keyframes {
|
||||||
|
values: vec![
|
||||||
|
Keyframe {
|
||||||
|
id: "1".into(),
|
||||||
|
value: 0.0,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "2".into(),
|
||||||
|
value: 100.0,
|
||||||
|
offset: 1.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "3".into(),
|
||||||
|
value: 300.0,
|
||||||
|
offset: 3.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let keyframes2 = Keyframes {
|
||||||
|
values: vec![
|
||||||
|
Keyframe {
|
||||||
|
id: "4".into(),
|
||||||
|
value: -100.0,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "5".into(),
|
||||||
|
value: 0.0,
|
||||||
|
offset: 1.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let rendered_keyframes1: Vec<RenderedKeyframe> = keyframes1
|
||||||
|
.values
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, keyframe)| {
|
||||||
|
let rendered_keyframe = render_keyframe(keyframe, &animation_data, index, 120, 60);
|
||||||
|
rendered_keyframe
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let rendered_keyframes2: Vec<RenderedKeyframe> = keyframes2
|
||||||
|
.values
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, keyframe)| {
|
||||||
|
let rendered_keyframe = render_keyframe(keyframe, &animation_data, index, 120, 60);
|
||||||
|
rendered_keyframe
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let val1 = interpolate_rendered_keyframes(
|
||||||
|
rendered_keyframes1.get(1).unwrap(),
|
||||||
|
rendered_keyframes1.get(2).unwrap(),
|
||||||
|
120,
|
||||||
|
InterpolationType::Linear,
|
||||||
|
fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let _val2 = interpolate_rendered_keyframes(
|
||||||
|
rendered_keyframes2.get(0).unwrap(),
|
||||||
|
rendered_keyframes2.get(1).unwrap(),
|
||||||
|
30,
|
||||||
|
InterpolationType::Linear,
|
||||||
|
fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(val1, 200.0);
|
||||||
|
//println!("{0}", val2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn calculates_the_spring_value() {
|
||||||
|
let _fps = 60;
|
||||||
|
let previous_value = 0.0;
|
||||||
|
let next_value = 500.0;
|
||||||
|
|
||||||
|
let mut spring_props = SpringProperties {
|
||||||
|
mass: 1.0, // Mass of the object attached to the spring
|
||||||
|
stiffness: 100.0, // Stiffness of the spring
|
||||||
|
damping: 10.0, // Damping factor of the spring
|
||||||
|
};
|
||||||
|
|
||||||
|
let value1 =
|
||||||
|
calculate_spring_value(100, previous_value, next_value, 100, 300, &mut spring_props);
|
||||||
|
let value2 =
|
||||||
|
calculate_spring_value(150, previous_value, next_value, 100, 300, &mut spring_props);
|
||||||
|
let value3 =
|
||||||
|
calculate_spring_value(200, previous_value, next_value, 100, 300, &mut spring_props);
|
||||||
|
|
||||||
|
println!("{value1}");
|
||||||
|
println!("{value2}");
|
||||||
|
println!("{value3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn converts_timestamp_to_frame() {
|
||||||
|
let frame1 = timestamp_to_frame(0.0, 60);
|
||||||
|
let frame2 = timestamp_to_frame(1.0, 60);
|
||||||
|
let frame3 = timestamp_to_frame(1.5, 60);
|
||||||
|
|
||||||
|
assert_eq!(frame1, 0);
|
||||||
|
assert_eq!(frame2, 60);
|
||||||
|
assert_eq!(frame3, 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn gets_value_at_frame() {
|
||||||
|
let animation_data = AnimationData {
|
||||||
|
offset: 0.0,
|
||||||
|
duration: 5.0,
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let fps = 60;
|
||||||
|
|
||||||
|
let keyframes = Keyframes {
|
||||||
|
values: vec![
|
||||||
|
Keyframe {
|
||||||
|
id: "1".into(),
|
||||||
|
value: 0.0,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "2".into(),
|
||||||
|
value: 100.0,
|
||||||
|
offset: 1.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "3".into(),
|
||||||
|
value: 300.0,
|
||||||
|
offset: 3.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
let value1 = keyframes.get_value_at_frame(50, &animation_data, fps);
|
||||||
|
let value2 = keyframes.get_value_at_frame(90, &animation_data, fps);
|
||||||
|
let value3 = keyframes.get_value_at_frame(120, &animation_data, fps);
|
||||||
|
let value4 = keyframes.get_value_at_frame(180, &animation_data, fps);
|
||||||
|
let value5 = keyframes.get_value_at_frame(220, &animation_data, fps);
|
||||||
|
println!("value1: {0}", value1);
|
||||||
|
println!("value2: {0}", value2);
|
||||||
|
println!("value3: {0}", value3);
|
||||||
|
println!("value4: {0}", value4);
|
||||||
|
println!("value5: {0}", value5);
|
||||||
|
}
|
64
lib/creator_rs/src/animation/primitives/transform.rs
Normal file
64
lib/creator_rs/src/animation/primitives/transform.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use super::{
|
||||||
|
entities::common::AnimationData,
|
||||||
|
values::animated_values::{AnimatedFloatVec2, AnimatedFloatVec3, AnimatedValue},
|
||||||
|
};
|
||||||
|
use crate::animation::timeline::Timeline;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct AnimatedTransform {
|
||||||
|
pub translate: AnimatedFloatVec2,
|
||||||
|
pub scale: AnimatedFloatVec2,
|
||||||
|
pub skew: AnimatedFloatVec2,
|
||||||
|
pub rotate: AnimatedFloatVec3,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct Transform {
|
||||||
|
pub translate: (f32, f32),
|
||||||
|
pub scale: (f32, f32),
|
||||||
|
pub skew: (f32, f32),
|
||||||
|
pub rotate: (f32, f32, f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedTransform {
|
||||||
|
pub fn sort_keyframes(&mut self) {
|
||||||
|
self.rotate.sort_keyframes();
|
||||||
|
self.skew.sort_keyframes();
|
||||||
|
self.scale.sort_keyframes();
|
||||||
|
self.translate.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate(&mut self, timeline: &Timeline, animation_data: &AnimationData) -> Transform {
|
||||||
|
let skew = self.skew.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let scale = self.scale.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let translate = self.translate.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
let rotate = self.rotate.get_value_at_frame(
|
||||||
|
timeline.render_state.curr_frame,
|
||||||
|
animation_data,
|
||||||
|
timeline.fps,
|
||||||
|
);
|
||||||
|
|
||||||
|
Transform {
|
||||||
|
skew,
|
||||||
|
scale,
|
||||||
|
translate,
|
||||||
|
rotate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
lib/creator_rs/src/animation/primitives/utils.rs
Normal file
29
lib/creator_rs/src/animation/primitives/utils.rs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
use super::{
|
||||||
|
entities::common::AnimationData,
|
||||||
|
keyframe::{Keyframe, RenderedKeyframe},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn timestamp_to_frame(timestamp: f32, fps: i16) -> i32 {
|
||||||
|
return (timestamp * fps as f32).round() as i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_keyframe(
|
||||||
|
keyframe: Keyframe,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
index: usize,
|
||||||
|
curr_frame: i32,
|
||||||
|
fps: i16,
|
||||||
|
) -> RenderedKeyframe {
|
||||||
|
let animation_start_frame = timestamp_to_frame(animation_data.offset, fps);
|
||||||
|
let frame_offset = timestamp_to_frame(keyframe.offset, fps);
|
||||||
|
let absolute_frame = animation_start_frame + frame_offset;
|
||||||
|
let distance_from_curr = absolute_frame - curr_frame;
|
||||||
|
|
||||||
|
RenderedKeyframe {
|
||||||
|
absolute_frame,
|
||||||
|
keyframe,
|
||||||
|
index,
|
||||||
|
distance_from_curr,
|
||||||
|
abs_distance_from_curr: distance_from_curr.abs(),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,268 @@
|
|||||||
|
use crate::animation::primitives::{
|
||||||
|
entities::common::AnimationData,
|
||||||
|
keyframe::{Keyframe, Keyframes},
|
||||||
|
};
|
||||||
|
#[cfg(feature = "parallelization")]
|
||||||
|
use rayon::prelude::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::values::{Float, FloatVec2, FloatVec3};
|
||||||
|
|
||||||
|
pub trait AnimatedValue<T> {
|
||||||
|
fn sort_keyframes(&mut self);
|
||||||
|
fn get_value_at_frame(&self, curr_frame: i32, animation_data: &AnimationData, fps: i16) -> T;
|
||||||
|
fn get_values_at_frame_range(
|
||||||
|
&self,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<T>;
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct AnimatedFloat {
|
||||||
|
pub keyframes: Keyframes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct AnimatedFloatVec2 {
|
||||||
|
pub keyframes: (AnimatedFloat, AnimatedFloat),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
pub struct AnimatedFloatVec3 {
|
||||||
|
pub keyframes: (AnimatedFloat, AnimatedFloat, AnimatedFloat),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||||
|
pub fn get_values_at_frame_range_from_animated_float(
|
||||||
|
animated_value: AnimatedFloat,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<Float> {
|
||||||
|
animated_value.get_values_at_frame_range(start_frame, end_frame, &animation_data, fps)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||||
|
pub fn get_values_at_frame_range_from_animated_float_vec2(
|
||||||
|
animated_value: AnimatedFloatVec2,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<FloatVec2> {
|
||||||
|
animated_value.get_values_at_frame_range(start_frame, end_frame, &animation_data, fps)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||||
|
pub fn get_values_at_frame_range_from_animated_float_vec3(
|
||||||
|
animated_value: AnimatedFloatVec3,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<FloatVec3> {
|
||||||
|
animated_value.get_values_at_frame_range(start_frame, end_frame, &animation_data, fps)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedFloat {
|
||||||
|
pub fn new(val: f32) -> AnimatedFloat {
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![Keyframe {
|
||||||
|
id: Uuid::new_v4().to_string().into(),
|
||||||
|
value: val,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedFloatVec2 {
|
||||||
|
pub fn new(x: f32, y: f32) -> AnimatedFloatVec2 {
|
||||||
|
AnimatedFloatVec2 {
|
||||||
|
keyframes: (AnimatedFloat::new(x), AnimatedFloat::new(y)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedFloatVec3 {
|
||||||
|
pub fn new(x: f32, y: f32, z: f32) -> AnimatedFloatVec3 {
|
||||||
|
AnimatedFloatVec3 {
|
||||||
|
keyframes: (
|
||||||
|
AnimatedFloat::new(x),
|
||||||
|
AnimatedFloat::new(y),
|
||||||
|
AnimatedFloat::new(z),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedValue<f32> for AnimatedFloat {
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
self.keyframes.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_value_at_frame(&self, curr_frame: i32, animation_data: &AnimationData, fps: i16) -> f32 {
|
||||||
|
self.keyframes
|
||||||
|
.get_value_at_frame(curr_frame, &animation_data, fps)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "parallelization")]
|
||||||
|
fn get_values_at_frame_range(
|
||||||
|
&self,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<f32> {
|
||||||
|
let values = (start_frame..end_frame)
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|i| self.get_value_at_frame(i, animation_data, fps))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
values
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "parallelization"))]
|
||||||
|
fn get_values_at_frame_range(
|
||||||
|
&self,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<f32> {
|
||||||
|
let values = (start_frame..end_frame)
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| self.get_value_at_frame(i, animation_data, fps))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedValue<(f32, f32, f32)> for AnimatedFloatVec3 {
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
self.keyframes.0.sort_keyframes();
|
||||||
|
self.keyframes.1.sort_keyframes();
|
||||||
|
self.keyframes.2.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_value_at_frame(
|
||||||
|
&self,
|
||||||
|
curr_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> (f32, f32, f32) {
|
||||||
|
let x = self
|
||||||
|
.keyframes
|
||||||
|
.0
|
||||||
|
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||||
|
|
||||||
|
let y = self
|
||||||
|
.keyframes
|
||||||
|
.1
|
||||||
|
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||||
|
|
||||||
|
let z = self
|
||||||
|
.keyframes
|
||||||
|
.2
|
||||||
|
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||||
|
|
||||||
|
return (x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_values_at_frame_range(
|
||||||
|
&self,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<(f32, f32, f32)> {
|
||||||
|
let x =
|
||||||
|
self.keyframes
|
||||||
|
.0
|
||||||
|
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||||
|
let y =
|
||||||
|
self.keyframes
|
||||||
|
.1
|
||||||
|
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||||
|
let z =
|
||||||
|
self.keyframes
|
||||||
|
.2
|
||||||
|
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||||
|
|
||||||
|
let vectors: Vec<(f32, f32, f32)> = x
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, val_x)| {
|
||||||
|
let val_y: f32 = *y.get(index).unwrap();
|
||||||
|
let val_z: f32 = *z.get(index).unwrap();
|
||||||
|
|
||||||
|
(val_x, val_y, val_z)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
vectors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimatedValue<(f32, f32)> for AnimatedFloatVec2 {
|
||||||
|
fn sort_keyframes(&mut self) {
|
||||||
|
self.keyframes.0.sort_keyframes();
|
||||||
|
self.keyframes.1.sort_keyframes();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_value_at_frame(
|
||||||
|
&self,
|
||||||
|
curr_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> (f32, f32) {
|
||||||
|
let x = self
|
||||||
|
.keyframes
|
||||||
|
.0
|
||||||
|
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||||
|
|
||||||
|
let y = self
|
||||||
|
.keyframes
|
||||||
|
.1
|
||||||
|
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||||
|
|
||||||
|
return (x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_values_at_frame_range(
|
||||||
|
&self,
|
||||||
|
start_frame: i32,
|
||||||
|
end_frame: i32,
|
||||||
|
animation_data: &AnimationData,
|
||||||
|
fps: i16,
|
||||||
|
) -> Vec<(f32, f32)> {
|
||||||
|
let x =
|
||||||
|
self.keyframes
|
||||||
|
.0
|
||||||
|
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||||
|
let y =
|
||||||
|
self.keyframes
|
||||||
|
.1
|
||||||
|
.get_values_at_frame_range(start_frame, end_frame, animation_data, fps);
|
||||||
|
|
||||||
|
let vectors: Vec<(f32, f32)> = x
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, val_x)| {
|
||||||
|
let val_y: f32 = *y.get(index).unwrap();
|
||||||
|
|
||||||
|
(val_x, val_y)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
vectors
|
||||||
|
}
|
||||||
|
}
|
2
lib/creator_rs/src/animation/primitives/values/mod.rs
Normal file
2
lib/creator_rs/src/animation/primitives/values/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod animated_values;
|
||||||
|
pub mod values;
|
4
lib/creator_rs/src/animation/primitives/values/values.rs
Normal file
4
lib/creator_rs/src/animation/primitives/values/values.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub type Float = f32;
|
||||||
|
pub type FloatVec2 = (f32, f32);
|
||||||
|
pub type FloatVec3 = (f32, f32, f32);
|
||||||
|
pub type FloatVec4 = (f32, f32, f32, f32);
|
318
lib/creator_rs/src/animation/timeline.rs
Normal file
318
lib/creator_rs/src/animation/timeline.rs
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
use super::primitives::{
|
||||||
|
entities::{
|
||||||
|
common::{AnimatedEntity, AnimationData, Cache, Entity},
|
||||||
|
rect::AnimatedRectEntity,
|
||||||
|
text::AnimatedTextEntity,
|
||||||
|
},
|
||||||
|
paint::{Color, FillStyle, Paint, PaintStyle, StrokeStyle, TextAlign, TextPaint},
|
||||||
|
values::animated_values::{AnimatedFloat, AnimatedFloatVec2},
|
||||||
|
};
|
||||||
|
use crate::animation::primitives::{
|
||||||
|
interpolations::{EasingFunction, InterpolationType, SpringProperties},
|
||||||
|
keyframe::{Keyframe, Keyframes},
|
||||||
|
};
|
||||||
|
#[cfg(feature = "parallelization")]
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct Input {
|
||||||
|
pub title: String,
|
||||||
|
pub sub_title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Timeline {
|
||||||
|
entities: Vec<AnimatedEntity>,
|
||||||
|
pub render_state: RenderState,
|
||||||
|
pub duration: f32,
|
||||||
|
pub fps: i16,
|
||||||
|
pub size: (i32, i32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct RenderState {
|
||||||
|
pub curr_frame: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Timeline {
|
||||||
|
#[cfg(feature = "parallelization")]
|
||||||
|
fn calculate(&self) -> Vec<Entity> {
|
||||||
|
let mut entities = self.entities.clone();
|
||||||
|
|
||||||
|
let entities = entities
|
||||||
|
.par_iter_mut()
|
||||||
|
.map(|entity| entity.calculate(self))
|
||||||
|
.filter(|entity| entity.is_some())
|
||||||
|
.map(|entity| entity.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "parallelization"))]
|
||||||
|
fn calculate(&self) -> Vec<Entity> {
|
||||||
|
let mut entities = self.entities.clone();
|
||||||
|
|
||||||
|
let entities = entities
|
||||||
|
.iter_mut()
|
||||||
|
.map(|entity| entity.calculate(self))
|
||||||
|
.filter(|entity| entity.is_some())
|
||||||
|
.map(|entity| entity.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
return entities;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||||
|
let bg_box = AnimatedRectEntity {
|
||||||
|
id: String::from_str("1").unwrap(),
|
||||||
|
paint,
|
||||||
|
animation_data: AnimationData {
|
||||||
|
offset: 0.0 + offset,
|
||||||
|
duration: 5.0,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
cache: Cache { valid: false },
|
||||||
|
transform: None,
|
||||||
|
origin: AnimatedFloatVec2::new(1280.0 / 2.0, 720.0 / 2.0),
|
||||||
|
position: AnimatedFloatVec2 {
|
||||||
|
keyframes: (
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![
|
||||||
|
Keyframe {
|
||||||
|
id: "1".into(),
|
||||||
|
value: (size.0 * -1) as f32,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: Some(InterpolationType::EasingFunction(
|
||||||
|
EasingFunction::QuintOut,
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "2".into(),
|
||||||
|
value: 0.0,
|
||||||
|
offset: 5.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![Keyframe {
|
||||||
|
id: "3".into(),
|
||||||
|
value: 0.0,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
size: AnimatedFloatVec2 {
|
||||||
|
keyframes: (
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![Keyframe {
|
||||||
|
id: "4".into(),
|
||||||
|
interpolation: None,
|
||||||
|
value: size.0 as f32,
|
||||||
|
offset: 0.0,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![Keyframe {
|
||||||
|
id: "5".into(),
|
||||||
|
value: size.1 as f32,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return bg_box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||||
|
pub fn calculate_timeline_at_curr_frame(timeline: Timeline) -> Vec<Entity> {
|
||||||
|
timeline.calculate()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn calculate_timeline_from_json_at_curr_frame(timeline_json: &str) -> String {
|
||||||
|
// TODO: Handle failure instead of unwrap
|
||||||
|
let timeline: Timeline = serde_json::from_str(timeline_json).unwrap();
|
||||||
|
|
||||||
|
let entities = calculate_timeline_at_curr_frame(timeline);
|
||||||
|
|
||||||
|
// TODO: Handle failure instead of unwrap
|
||||||
|
let entities_json = serde_json::to_string(&entities).unwrap();
|
||||||
|
|
||||||
|
entities_json
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_timeline_entities_at_frame(
|
||||||
|
render_state: RenderState,
|
||||||
|
size: (i32, i32),
|
||||||
|
input: Input,
|
||||||
|
) -> Vec<Entity> {
|
||||||
|
let rect1_paint = Paint {
|
||||||
|
style: PaintStyle::Fill(FillStyle {
|
||||||
|
color: Color::new(34, 189, 58, 1.0),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rect2_paint = Paint {
|
||||||
|
style: PaintStyle::Fill(FillStyle {
|
||||||
|
color: Color::new(23, 178, 28, 1.0),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let rect3_paint = Paint {
|
||||||
|
style: PaintStyle::Fill(FillStyle {
|
||||||
|
color: Color::new(43, 128, 98, 1.0),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
let title_paint = TextPaint {
|
||||||
|
style: PaintStyle::Stroke(StrokeStyle {
|
||||||
|
color: Color::new(0, 0, 0, 1.0),
|
||||||
|
width: 10.0,
|
||||||
|
}),
|
||||||
|
font_name: "Arial".into(),
|
||||||
|
align: TextAlign::Center,
|
||||||
|
size: 20.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let sub_title_paint = TextPaint {
|
||||||
|
style: PaintStyle::Fill(FillStyle {
|
||||||
|
color: Color::new(0, 0, 0, 1.0),
|
||||||
|
}),
|
||||||
|
font_name: "Arial".into(),
|
||||||
|
align: TextAlign::Center,
|
||||||
|
size: 10.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let timeline = Timeline {
|
||||||
|
fps: 60,
|
||||||
|
duration: 5.0,
|
||||||
|
size,
|
||||||
|
entities: vec![
|
||||||
|
AnimatedEntity::Rect(build_bg(0.0, rect1_paint, size)),
|
||||||
|
AnimatedEntity::Rect(build_bg(0.5, rect2_paint, size)),
|
||||||
|
AnimatedEntity::Rect(build_bg(1.0, rect3_paint, size)),
|
||||||
|
AnimatedEntity::Text(AnimatedTextEntity {
|
||||||
|
id: String::from_str("2").unwrap(),
|
||||||
|
paint: title_paint,
|
||||||
|
cache: Cache { valid: false },
|
||||||
|
text: input.title,
|
||||||
|
animation_data: AnimationData {
|
||||||
|
offset: 0.0,
|
||||||
|
duration: 6.0,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
transform: None,
|
||||||
|
origin: AnimatedFloatVec2 {
|
||||||
|
keyframes: (
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![
|
||||||
|
Keyframe {
|
||||||
|
id: "1".into(),
|
||||||
|
value: 0.0,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: Some(InterpolationType::Spring(
|
||||||
|
SpringProperties {
|
||||||
|
mass: 1.0,
|
||||||
|
damping: 20.0,
|
||||||
|
stiffness: 200.0,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "2".into(),
|
||||||
|
value: (size.0 / 2) as f32,
|
||||||
|
offset: 2.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![Keyframe {
|
||||||
|
id: "3".into(),
|
||||||
|
value: (size.1 / 2) as f32,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
AnimatedEntity::Text(AnimatedTextEntity {
|
||||||
|
id: String::from_str("3").unwrap(),
|
||||||
|
paint: sub_title_paint,
|
||||||
|
text: input.sub_title,
|
||||||
|
cache: Cache { valid: false },
|
||||||
|
animation_data: AnimationData {
|
||||||
|
offset: 0.5,
|
||||||
|
duration: 6.0,
|
||||||
|
visible: true,
|
||||||
|
},
|
||||||
|
transform: None,
|
||||||
|
origin: AnimatedFloatVec2 {
|
||||||
|
keyframes: (
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![
|
||||||
|
Keyframe {
|
||||||
|
id: "5".into(),
|
||||||
|
value: 0.0,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: Some(InterpolationType::Spring(
|
||||||
|
SpringProperties {
|
||||||
|
mass: 1.0,
|
||||||
|
damping: 20.0,
|
||||||
|
stiffness: 200.0,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Keyframe {
|
||||||
|
id: "6".into(),
|
||||||
|
|
||||||
|
value: (size.0 / 2) as f32,
|
||||||
|
offset: 2.0,
|
||||||
|
interpolation: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AnimatedFloat {
|
||||||
|
keyframes: Keyframes {
|
||||||
|
values: vec![Keyframe {
|
||||||
|
id: "7".into(),
|
||||||
|
value: ((size.1 / 2) as f32) + 80.0,
|
||||||
|
offset: 0.0,
|
||||||
|
interpolation: None,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
render_state: render_state,
|
||||||
|
};
|
||||||
|
|
||||||
|
timeline.calculate()
|
||||||
|
}
|
58
lib/creator_rs/src/fonts/fonts.rs
Normal file
58
lib/creator_rs/src/fonts/fonts.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use font_kit::source::SystemSource;
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||||
|
pub fn get_system_fonts() -> Option<Vec<String>> {
|
||||||
|
let source = SystemSource::new();
|
||||||
|
|
||||||
|
let found_fonts = source.all_fonts();
|
||||||
|
|
||||||
|
match found_fonts {
|
||||||
|
Ok(found_fonts) => {
|
||||||
|
let font_names: Vec<String> = found_fonts
|
||||||
|
.iter()
|
||||||
|
.map(|f| f.load())
|
||||||
|
.filter(|f| f.is_ok())
|
||||||
|
.map(|f| f.unwrap())
|
||||||
|
.map(|f| f.postscript_name())
|
||||||
|
.filter(|f| f.is_some())
|
||||||
|
.map(|f| f.unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Some(font_names)
|
||||||
|
}
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||||
|
pub fn get_system_families() -> Option<Vec<String>> {
|
||||||
|
let source = SystemSource::new();
|
||||||
|
|
||||||
|
let found_families = source.all_families();
|
||||||
|
|
||||||
|
found_families.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "tauri", tauri::command)]
|
||||||
|
pub fn get_system_font(font_name: String) -> Option<Vec<u8>> {
|
||||||
|
let source = SystemSource::new();
|
||||||
|
|
||||||
|
let font = source.select_by_postscript_name(font_name.as_str());
|
||||||
|
|
||||||
|
match font {
|
||||||
|
Ok(font) => {
|
||||||
|
let font = font.load();
|
||||||
|
|
||||||
|
if let Ok(font) = font {
|
||||||
|
if let Some(font_data) = font.copy_font_data() {
|
||||||
|
Some(font_data.as_slice().to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => panic!("Err"),
|
||||||
|
}
|
||||||
|
}
|
1
lib/creator_rs/src/fonts/mod.rs
Normal file
1
lib/creator_rs/src/fonts/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod fonts;
|
3
lib/creator_rs/src/lib.rs
Normal file
3
lib/creator_rs/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod animation;
|
||||||
|
#[cfg(feature = "fonts")]
|
||||||
|
pub mod fonts;
|
8
lib/something/Cargo.toml
Normal file
8
lib/something/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "something"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
14
lib/something/src/lib.rs
Normal file
14
lib/something/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user