add staggered text
rename box to rect refactor rust
This commit is contained in:
parent
7576850ae0
commit
8523e44029
BIN
app/.yarn/install-state.gz
Normal file
BIN
app/.yarn/install-state.gz
Normal file
Binary file not shown.
1
app/.yarnrc.yml
Normal file
1
app/.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
||||
nodeLinker: node-modules
|
Binary file not shown.
@ -1,241 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::animation::timeline::Timeline;
|
||||
|
||||
use super::{
|
||||
paint::{Paint, TextPaint},
|
||||
utils::timestamp_to_frame,
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
};
|
||||
|
||||
//#region Animateable Objects
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum AnimatedEntity {
|
||||
Text(AnimatedTextEntity),
|
||||
Ellipse(AnimatedEllipseEntity),
|
||||
Box(AnimatedBoxEntity),
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Entity {
|
||||
Text(TextEntity),
|
||||
Ellipse(EllipseEntity),
|
||||
Box(BoxEntity),
|
||||
}
|
||||
|
||||
impl AnimatedEntity {
|
||||
pub fn calculate(&mut self, timeline: &Timeline) -> Option<Entity> {
|
||||
match self {
|
||||
Self::Text(text_entity) => text_entity.calculate(timeline),
|
||||
Self::Box(box_entity) => box_entity.calculate(timeline),
|
||||
Self::Ellipse(ellipse_entity) => ellipse_entity.calculate(timeline),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimatedTextEntity {
|
||||
pub text: String,
|
||||
pub origin: AnimatedFloatVec2,
|
||||
pub paint: TextPaint,
|
||||
pub animation_data: AnimationData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TextEntity {
|
||||
pub text: String,
|
||||
pub origin: (f32, f32),
|
||||
pub paint: TextPaint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimatedBoxEntity {
|
||||
pub position: AnimatedFloatVec2,
|
||||
pub size: AnimatedFloatVec2,
|
||||
pub origin: AnimatedFloatVec2,
|
||||
pub paint: Paint,
|
||||
pub animation_data: AnimationData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct BoxEntity {
|
||||
pub position: (f32, f32),
|
||||
pub size: (f32, f32),
|
||||
pub origin: (f32, f32),
|
||||
pub paint: Paint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimatedEllipseEntity {
|
||||
pub paint: Paint,
|
||||
pub radius: AnimatedFloatVec2,
|
||||
pub origin: AnimatedFloatVec2,
|
||||
pub position: AnimatedFloatVec2,
|
||||
pub animation_data: AnimationData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct EllipseEntity {
|
||||
pub radius: (f32, f32),
|
||||
pub position: (f32, f32),
|
||||
pub origin: (f32, f32),
|
||||
pub paint: Paint,
|
||||
}
|
||||
|
||||
pub trait Animateable {
|
||||
fn sort_keyframes(&mut self);
|
||||
|
||||
fn calculate(&mut self, timeline: &Timeline) -> Option<Entity>;
|
||||
|
||||
// Checks if the Box is visible and should be drawn
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
TextEntity {
|
||||
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) {
|
||||
self.origin.sort_keyframes();
|
||||
}
|
||||
}
|
||||
|
||||
impl Animateable for AnimatedBoxEntity {
|
||||
fn sort_keyframes(&mut self) {
|
||||
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,
|
||||
);
|
||||
|
||||
Some(Entity::Box(BoxEntity {
|
||||
position,
|
||||
size,
|
||||
origin,
|
||||
paint: self.paint.clone(),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
Some(Entity::Ellipse(EllipseEntity {
|
||||
radius,
|
||||
position,
|
||||
origin,
|
||||
paint: self.paint.clone(),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.position.sort_keyframes();
|
||||
self.radius.sort_keyframes();
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimationData {
|
||||
pub offset: f32,
|
||||
pub duration: f32,
|
||||
pub visible: bool,
|
||||
}
|
73
app/src-tauri/src/animation/primitives/entities/common.rs
Normal file
73
app/src-tauri/src/animation/primitives/entities/common.rs
Normal file
@ -0,0 +1,73 @@
|
||||
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)]
|
||||
#[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,
|
||||
}
|
86
app/src-tauri/src/animation/primitives/entities/ellipse.rs
Normal file
86
app/src-tauri/src/animation/primitives/entities/ellipse.rs
Normal file
@ -0,0 +1,86 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::animation::{
|
||||
primitives::{
|
||||
paint::Paint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
},
|
||||
timeline::Timeline,
|
||||
};
|
||||
|
||||
use super::common::{Animateable, AnimationData, Drawable, Entity};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimatedEllipseEntity {
|
||||
pub paint: Paint,
|
||||
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 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 {
|
||||
radius,
|
||||
position,
|
||||
origin,
|
||||
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
app/src-tauri/src/animation/primitives/entities/mod.rs
Normal file
5
app/src-tauri/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;
|
84
app/src-tauri/src/animation/primitives/entities/rect.rs
Normal file
84
app/src-tauri/src/animation/primitives/entities/rect.rs
Normal file
@ -0,0 +1,84 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::animation::{
|
||||
primitives::{
|
||||
paint::Paint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
},
|
||||
timeline::Timeline,
|
||||
};
|
||||
|
||||
use super::common::{Animateable, AnimationData, Drawable, Entity};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimatedRectEntity {
|
||||
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 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 {
|
||||
position,
|
||||
size,
|
||||
origin,
|
||||
paint: self.paint.clone(),
|
||||
transform,
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
use super::common::{Animateable, AnimationData, Drawable, Entity};
|
||||
use crate::animation::{
|
||||
primitives::{
|
||||
paint::TextPaint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
},
|
||||
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<Transform>,
|
||||
pub paint: TextPaint,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimatedStaggeredTextEntity {
|
||||
pub text: String,
|
||||
pub stagger: f32,
|
||||
pub animation_data: AnimationData,
|
||||
pub transform: Option<AnimatedTransform>,
|
||||
pub letter: AnimatedStaggeredTextLetter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct StaggeredTextEntity {
|
||||
pub text: String,
|
||||
pub stagger: 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,
|
||||
};
|
||||
|
||||
let letter_transform: Option<Transform> = match self.letter.transform.clone() {
|
||||
Some(mut val) => Some(val.calculate(timeline, &self.animation_data)),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Some(Entity::StaggeredText(StaggeredTextEntity {
|
||||
transform,
|
||||
stagger: self.stagger,
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
76
app/src-tauri/src/animation/primitives/entities/text.rs
Normal file
76
app/src-tauri/src/animation/primitives/entities/text.rs
Normal file
@ -0,0 +1,76 @@
|
||||
use crate::animation::{
|
||||
primitives::{
|
||||
paint::TextPaint,
|
||||
transform::{AnimatedTransform, Transform},
|
||||
values::{AnimatedFloatVec2, AnimatedValue},
|
||||
},
|
||||
timeline::Timeline,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::common::{Animateable, AnimationData, Drawable, Entity};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TextEntity {
|
||||
pub text: String,
|
||||
pub origin: (f32, f32),
|
||||
pub paint: TextPaint,
|
||||
pub transform: Option<Transform>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AnimatedTextEntity {
|
||||
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 {
|
||||
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();
|
||||
}
|
||||
}
|
@ -8,20 +8,20 @@ use simple_easing::{
|
||||
quint_in_out, quint_out,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[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)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SpringState {
|
||||
pub velocity: f32,
|
||||
pub last_val: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "easing_function")]
|
||||
pub enum EasingFunction {
|
||||
QuintOut,
|
||||
@ -69,7 +69,7 @@ impl EasingFunction {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum InterpolationType {
|
||||
Linear,
|
||||
|
@ -3,12 +3,12 @@ use std::cmp::Ordering;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
entities::AnimationData,
|
||||
entities::common::AnimationData,
|
||||
interpolations::{interpolate_rendered_keyframes, InterpolationType},
|
||||
utils::render_keyframe,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Keyframe {
|
||||
pub value: f32,
|
||||
pub offset: f32,
|
||||
@ -24,7 +24,7 @@ pub struct RenderedKeyframe {
|
||||
pub abs_distance_from_curr: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Keyframes {
|
||||
pub values: Vec<Keyframe>,
|
||||
}
|
||||
|
@ -3,5 +3,6 @@ pub mod interpolations;
|
||||
pub mod keyframe;
|
||||
pub mod paint;
|
||||
pub mod tests;
|
||||
pub mod transform;
|
||||
pub mod utils;
|
||||
pub mod values;
|
||||
|
@ -1,6 +1,6 @@
|
||||
#[cfg(test)]
|
||||
use crate::animation::primitives::{
|
||||
entities::AnimationData,
|
||||
entities::common::AnimationData,
|
||||
interpolations::{calculate_spring_value, SpringProperties},
|
||||
keyframe::{Keyframe, Keyframes},
|
||||
utils::timestamp_to_frame,
|
||||
|
64
app/src-tauri/src/animation/primitives/transform.rs
Normal file
64
app/src-tauri/src/animation/primitives/transform.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use super::{
|
||||
entities::common::AnimationData,
|
||||
values::{AnimatedFloatVec2, 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: AnimatedFloatVec2,
|
||||
}
|
||||
|
||||
#[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),
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use super::{
|
||||
entities::AnimationData,
|
||||
entities::common::AnimationData,
|
||||
keyframe::{Keyframe, RenderedKeyframe},
|
||||
};
|
||||
|
||||
|
@ -1,24 +1,28 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
entities::AnimationData,
|
||||
entities::common::AnimationData,
|
||||
keyframe::{Keyframe, Keyframes},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait AnimatedValue<T> {
|
||||
fn sort_keyframes(&mut self);
|
||||
fn get_value_at_frame(&self, curr_frame: i32, animation_data: &AnimationData, fps: i16) -> T;
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct AnimatedFloat {
|
||||
pub keyframes: Keyframes,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[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),
|
||||
}
|
||||
|
||||
impl AnimatedFloat {
|
||||
pub fn new(val: f32) -> AnimatedFloat {
|
||||
AnimatedFloat {
|
||||
@ -41,6 +45,18 @@ impl AnimatedFloatVec2 {
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
@ -52,6 +68,38 @@ impl AnimatedValue<f32> for AnimatedFloat {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
.1
|
||||
.get_value_at_frame(curr_frame, animation_data, fps);
|
||||
|
||||
return (x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
impl AnimatedValue<(f32, f32)> for AnimatedFloatVec2 {
|
||||
fn sort_keyframes(&mut self) {
|
||||
self.keyframes.0.sort_keyframes();
|
||||
|
@ -1,5 +1,4 @@
|
||||
use crate::animation::primitives::{
|
||||
entities::{AnimatedBoxEntity, AnimatedEntity, AnimatedTextEntity, AnimationData},
|
||||
interpolations::{EasingFunction, InterpolationType, SpringProperties},
|
||||
keyframe::{Keyframe, Keyframes},
|
||||
};
|
||||
@ -7,7 +6,11 @@ use rayon::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::primitives::{
|
||||
entities::Entity,
|
||||
entities::{
|
||||
common::{AnimatedEntity, AnimationData, Entity},
|
||||
rect::AnimatedRectEntity,
|
||||
text::AnimatedTextEntity,
|
||||
},
|
||||
paint::{Color, FillStyle, Paint, PaintStyle, StrokeStyle, TextAlign, TextPaint},
|
||||
values::{AnimatedFloat, AnimatedFloatVec2},
|
||||
};
|
||||
@ -47,14 +50,15 @@ impl Timeline {
|
||||
}
|
||||
}
|
||||
|
||||
fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedBoxEntity {
|
||||
let bg_box = AnimatedBoxEntity {
|
||||
fn build_bg(offset: f32, paint: Paint, size: (i32, i32)) -> AnimatedRectEntity {
|
||||
let bg_box = AnimatedRectEntity {
|
||||
paint,
|
||||
animation_data: AnimationData {
|
||||
offset: 0.0 + offset,
|
||||
duration: 5.0,
|
||||
visible: true,
|
||||
},
|
||||
transform: None,
|
||||
origin: AnimatedFloatVec2::new(1280.0 / 2.0, 720.0 / 2.0),
|
||||
position: AnimatedFloatVec2 {
|
||||
keyframes: (
|
||||
@ -123,19 +127,19 @@ pub fn test_timeline_entities_at_frame(
|
||||
size: (i32, i32),
|
||||
input: Input,
|
||||
) -> Vec<Entity> {
|
||||
let box1_paint = Paint {
|
||||
let rect1_paint = Paint {
|
||||
style: PaintStyle::Fill(FillStyle {
|
||||
color: Color::new(34, 189, 58, 1.0),
|
||||
}),
|
||||
};
|
||||
|
||||
let box2_paint = Paint {
|
||||
let rect2_paint = Paint {
|
||||
style: PaintStyle::Fill(FillStyle {
|
||||
color: Color::new(23, 178, 28, 1.0),
|
||||
}),
|
||||
};
|
||||
|
||||
let box3_paint = Paint {
|
||||
let rect3_paint = Paint {
|
||||
style: PaintStyle::Fill(FillStyle {
|
||||
color: Color::new(43, 128, 98, 1.0),
|
||||
}),
|
||||
@ -163,9 +167,9 @@ pub fn test_timeline_entities_at_frame(
|
||||
duration: 5.0,
|
||||
size,
|
||||
entities: vec![
|
||||
AnimatedEntity::Box(build_bg(0.0, box1_paint, size)),
|
||||
AnimatedEntity::Box(build_bg(0.5, box2_paint, size)),
|
||||
AnimatedEntity::Box(build_bg(1.0, box3_paint, size)),
|
||||
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 {
|
||||
paint: title_paint,
|
||||
text: input.title,
|
||||
@ -174,6 +178,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
duration: 6.0,
|
||||
visible: true,
|
||||
},
|
||||
transform: None,
|
||||
origin: AnimatedFloatVec2 {
|
||||
keyframes: (
|
||||
AnimatedFloat {
|
||||
@ -218,6 +223,7 @@ pub fn test_timeline_entities_at_frame(
|
||||
duration: 6.0,
|
||||
visible: true,
|
||||
},
|
||||
transform: None,
|
||||
origin: AnimatedFloatVec2 {
|
||||
keyframes: (
|
||||
AnimatedFloat {
|
||||
|
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
Before Width: | Height: | Size: 4.0 KiB |
@ -56,7 +56,7 @@ const CanvasComponent: FC<CanvasProps> = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// console.time("calculation");
|
||||
console.time("calculation");
|
||||
const parsedEntities = AnimatedEntities.parse(entities);
|
||||
|
||||
invoke("calculate_timeline_entities_at_frame", {
|
||||
@ -68,11 +68,11 @@ const CanvasComponent: FC<CanvasProps> = () => {
|
||||
duration,
|
||||
},
|
||||
}).then((data) => {
|
||||
// console.timeEnd("calculation");
|
||||
console.timeEnd("calculation");
|
||||
// console.log(data);
|
||||
|
||||
const entitiesResult = Entities.safeParse(data);
|
||||
//console.time("draw");
|
||||
console.time("draw");
|
||||
|
||||
if (canvasKit && canvas.current && surface.current && fontData) {
|
||||
surface.current.flush();
|
||||
@ -101,7 +101,7 @@ const CanvasComponent: FC<CanvasProps> = () => {
|
||||
}
|
||||
});
|
||||
}
|
||||
// console.timeEnd("draw");
|
||||
console.timeEnd("draw");
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -37,7 +37,7 @@ const KeyframeIndicator: FC<{
|
||||
style={{
|
||||
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
|
||||
}}
|
||||
className="bg-indigo-300 absolute w-2 h-2 z-30 top-[39%]"
|
||||
className="bg-indigo-300 absolute w-2 h-2 z-30 top-[39%] select-none"
|
||||
></motion.div>
|
||||
);
|
||||
};
|
||||
@ -70,7 +70,7 @@ const Track: FC<TrackProps> = ({ keyframes, animationData, index, name }) => {
|
||||
</div>
|
||||
<div
|
||||
style={{ width: "1000px" }}
|
||||
className="flex w-full h-full flex-row relative bg-gray-900"
|
||||
className="flex w-full h-full flex-row relative bg-gray-900 select-none"
|
||||
>
|
||||
{keyframes.map((keyframe, index) => (
|
||||
<KeyframeIndicator
|
||||
@ -113,7 +113,7 @@ const Track: FC<TrackProps> = ({ keyframes, animationData, index, name }) => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="z-10 w-4 bg-slate-500 h-full absolute rounded-md"
|
||||
className="z-10 w-4 bg-slate-500 h-full absolute rounded-md select-none"
|
||||
/>
|
||||
<motion.div
|
||||
drag="x"
|
||||
@ -142,7 +142,7 @@ const Track: FC<TrackProps> = ({ keyframes, animationData, index, name }) => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="z-10 w-4 bg-slate-500 h-full absolute rounded-md"
|
||||
className="z-10 w-4 bg-slate-500 h-full absolute rounded-md select-none"
|
||||
/>
|
||||
<motion.div
|
||||
drag="x"
|
||||
@ -173,7 +173,7 @@ const Track: FC<TrackProps> = ({ keyframes, animationData, index, name }) => {
|
||||
},
|
||||
});
|
||||
}}
|
||||
className="z-5 h-full absolute rounded-md transition-colors bg-gray-700 hover:bg-gray-600"
|
||||
className="z-5 h-full absolute rounded-md transition-colors bg-gray-700 hover:bg-gray-600 select-none"
|
||||
></motion.div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,47 +0,0 @@
|
||||
import { Canvas, CanvasKit } from "canvaskit-wasm";
|
||||
import { z } from "zod";
|
||||
import { BoxEntity } from "primitives/Entities";
|
||||
import { buildPaintStyle } from "./paint";
|
||||
|
||||
export default function drawBox(
|
||||
CanvasKit: CanvasKit,
|
||||
canvas: Canvas,
|
||||
entity: z.infer<typeof BoxEntity>
|
||||
) {
|
||||
const paint = new CanvasKit.Paint();
|
||||
|
||||
const debugPaint = new CanvasKit.Paint();
|
||||
debugPaint.setColor(CanvasKit.RED);
|
||||
buildPaintStyle(CanvasKit, paint, entity.paint);
|
||||
|
||||
let targetPosition = entity.position;
|
||||
|
||||
canvas.drawCircle(targetPosition[0], targetPosition[1], 10, debugPaint);
|
||||
|
||||
targetPosition = targetPosition.map((val, index) => {
|
||||
let temp = val - entity.size[index] * 0.5;
|
||||
|
||||
return temp;
|
||||
});
|
||||
|
||||
debugPaint.setColor(CanvasKit.BLUE);
|
||||
|
||||
canvas.drawCircle(targetPosition[0], targetPosition[1], 10, debugPaint);
|
||||
|
||||
debugPaint.setColor(CanvasKit.GREEN);
|
||||
|
||||
canvas.drawCircle(targetPosition[0], targetPosition[1], 10, debugPaint);
|
||||
|
||||
console.log(targetPosition[0], targetPosition[1]);
|
||||
|
||||
const rect = CanvasKit.XYWHRect(
|
||||
targetPosition[0],
|
||||
targetPosition[1],
|
||||
entity.size[0],
|
||||
entity.size[1]
|
||||
);
|
||||
|
||||
canvas.drawRect(rect, paint);
|
||||
|
||||
canvas.drawCircle(targetPosition[0], targetPosition[1], 10, debugPaint);
|
||||
}
|
@ -13,9 +13,13 @@ export default function drawEllipse(
|
||||
|
||||
buildPaintStyle(CanvasKit, paint, entity.paint);
|
||||
|
||||
const mappedPosition = entity.position.map(
|
||||
(val, index) => val - entity.radius[index] * 0.5
|
||||
);
|
||||
|
||||
const rect = CanvasKit.XYWHRect(
|
||||
entity.position[0],
|
||||
entity.position[1],
|
||||
mappedPosition[0],
|
||||
mappedPosition[1],
|
||||
entity.radius[0],
|
||||
entity.radius[1]
|
||||
);
|
||||
|
43
app/src/drawers/rect.ts
Normal file
43
app/src/drawers/rect.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { Canvas, CanvasKit } from "canvaskit-wasm";
|
||||
import { z } from "zod";
|
||||
import { RectEntity } from "primitives/Entities";
|
||||
import { buildPaintStyle } from "./paint";
|
||||
|
||||
export default function drawRect(
|
||||
CanvasKit: CanvasKit,
|
||||
canvas: Canvas,
|
||||
entity: z.infer<typeof RectEntity>
|
||||
) {
|
||||
canvas.save();
|
||||
|
||||
const paint = new CanvasKit.Paint();
|
||||
|
||||
buildPaintStyle(CanvasKit, paint, entity.paint);
|
||||
|
||||
const mappedPosition = entity.position.map(
|
||||
(val, index) => val - entity.size[index] * 0.5
|
||||
);
|
||||
|
||||
const rect = CanvasKit.XYWHRect(
|
||||
mappedPosition[0],
|
||||
mappedPosition[1],
|
||||
entity.size[0],
|
||||
entity.size[1]
|
||||
);
|
||||
|
||||
if (entity.transform) {
|
||||
const origin = [0, entity.size[1]];
|
||||
|
||||
canvas.translate(origin[0], origin[1]);
|
||||
|
||||
canvas.scale(entity.transform.scale[0], entity.transform.scale[1]);
|
||||
|
||||
canvas.rotate;
|
||||
|
||||
canvas.translate(-origin[0], -origin[1]);
|
||||
}
|
||||
|
||||
canvas.drawRect(rect, paint);
|
||||
|
||||
canvas.restore();
|
||||
}
|
33
app/src/drawers/staggered-text.ts
Normal file
33
app/src/drawers/staggered-text.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { convertToFloat } from "@tempblade/common";
|
||||
import { Canvas, CanvasKit } from "canvaskit-wasm";
|
||||
import { StaggeredText } from "primitives/Entities";
|
||||
import { z } from "zod";
|
||||
|
||||
export default function drawStaggeredText(
|
||||
CanvasKit: CanvasKit,
|
||||
canvs: Canvas,
|
||||
entity: z.output<typeof StaggeredText>,
|
||||
fontData: ArrayBuffer
|
||||
) {
|
||||
const paint = new CanvasKit.Paint();
|
||||
|
||||
const color = convertToFloat(entity.letter.paint.style.color.value);
|
||||
|
||||
paint.setColor(color);
|
||||
|
||||
const typeface = CanvasKit.Typeface.MakeFreeTypeFaceFromData(fontData);
|
||||
|
||||
const font = new CanvasKit.Font(typeface, entity.letter.paint.size);
|
||||
|
||||
const glyphIDs = font.getGlyphIDs(entity.text);
|
||||
|
||||
font.setLinearMetrics(true);
|
||||
font.setSubpixel(true);
|
||||
font.setHinting(CanvasKit.FontHinting.Slight);
|
||||
|
||||
const bounds = font.getGlyphBounds(glyphIDs, paint);
|
||||
const widths = font.getGlyphWidths(glyphIDs, paint);
|
||||
|
||||
console.log(bounds);
|
||||
console.log(widths);
|
||||
}
|
@ -6,7 +6,7 @@ import { z } from "zod";
|
||||
export default function drawText(
|
||||
CanvasKit: CanvasKit,
|
||||
canvas: Canvas,
|
||||
entity: z.infer<typeof TextEntity>,
|
||||
entity: z.output<typeof TextEntity>,
|
||||
fontData: ArrayBuffer
|
||||
) {
|
||||
const fontMgr = CanvasKit.FontMgr.FromData(fontData);
|
||||
@ -38,5 +38,6 @@ export default function drawText(
|
||||
p.layout(900);
|
||||
const height = p.getHeight() / 2;
|
||||
const width = p.getMaxWidth() / 2;
|
||||
|
||||
canvas.drawParagraph(p, entity.origin[0] - width, entity.origin[1] - height);
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import { Timeline } from "primitives/Timeline";
|
||||
import { staticAnimatedNumber, staticAnimatedVec2 } from "primitives/Values";
|
||||
import { z } from "zod";
|
||||
|
||||
function buildBox1(
|
||||
function buildRect1(
|
||||
offset: number,
|
||||
color: z.infer<typeof Color>
|
||||
): z.input<typeof AnimatedEntity> {
|
||||
return {
|
||||
type: "Box",
|
||||
type: "Rect",
|
||||
paint: {
|
||||
style: {
|
||||
type: "Stroke",
|
||||
@ -52,58 +52,64 @@ function buildBox1(
|
||||
};
|
||||
}
|
||||
|
||||
function buildBox(
|
||||
function buildRect(
|
||||
offset: number,
|
||||
color: z.infer<typeof Color>
|
||||
): z.input<typeof AnimatedEntity> {
|
||||
return {
|
||||
type: "Box",
|
||||
type: "Rect",
|
||||
paint: {
|
||||
style: {
|
||||
type: "Fill",
|
||||
color,
|
||||
},
|
||||
},
|
||||
size: {
|
||||
keyframes: [
|
||||
{
|
||||
keyframes: {
|
||||
values: [
|
||||
{
|
||||
interpolation: {
|
||||
type: "Linear",
|
||||
},
|
||||
value: 1280.0,
|
||||
offset: 0.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
keyframes: {
|
||||
values: [
|
||||
{
|
||||
interpolation: {
|
||||
type: "EasingFunction",
|
||||
easing_function: "CircOut",
|
||||
},
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
},
|
||||
{
|
||||
interpolation: {
|
||||
type: "Linear",
|
||||
},
|
||||
value: 720.0,
|
||||
offset: 4.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
size: staticAnimatedVec2(1280, 720),
|
||||
origin: staticAnimatedVec2(0, -720),
|
||||
position: staticAnimatedVec2(1280 / 2, 720 / 2),
|
||||
transform: {
|
||||
translate: staticAnimatedVec2(0, 0),
|
||||
rotate: staticAnimatedVec2(0, 0),
|
||||
skew: staticAnimatedVec2(0, 0),
|
||||
scale: {
|
||||
keyframes: [
|
||||
{
|
||||
keyframes: {
|
||||
values: [
|
||||
{
|
||||
interpolation: {
|
||||
type: "Linear",
|
||||
},
|
||||
value: 1.0,
|
||||
offset: 0.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
keyframes: {
|
||||
values: [
|
||||
{
|
||||
interpolation: {
|
||||
type: "EasingFunction",
|
||||
easing_function: "CircOut",
|
||||
},
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
},
|
||||
{
|
||||
interpolation: {
|
||||
type: "Linear",
|
||||
},
|
||||
value: 1.0,
|
||||
offset: 4.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
animation_data: {
|
||||
offset,
|
||||
duration: 10.0,
|
||||
@ -165,18 +171,100 @@ function buildText(
|
||||
};
|
||||
}
|
||||
|
||||
function buildStaggeredText(
|
||||
text: string,
|
||||
offset: number,
|
||||
color: z.input<typeof Color>
|
||||
): z.input<typeof AnimatedEntity> {
|
||||
return {
|
||||
type: "StaggeredText",
|
||||
text,
|
||||
transform: {
|
||||
translate: staticAnimatedVec2(0, 0),
|
||||
rotate: staticAnimatedVec2(0, 0),
|
||||
skew: staticAnimatedVec2(0, 0),
|
||||
scale: staticAnimatedVec2(1, 1),
|
||||
},
|
||||
animation_data: {
|
||||
offset: 0,
|
||||
duration: 2,
|
||||
},
|
||||
stagger: 2.0,
|
||||
letter: {
|
||||
paint: {
|
||||
style: {
|
||||
type: "Fill",
|
||||
color,
|
||||
},
|
||||
size: 30,
|
||||
align: "Center",
|
||||
},
|
||||
transform: {
|
||||
translate: staticAnimatedVec2(0, 0),
|
||||
rotate: staticAnimatedVec2(0, 0),
|
||||
skew: staticAnimatedVec2(0, 0),
|
||||
scale: {
|
||||
keyframes: [
|
||||
{
|
||||
keyframes: {
|
||||
values: [
|
||||
{
|
||||
interpolation: {
|
||||
type: "Linear",
|
||||
},
|
||||
value: 1.0,
|
||||
offset: 0.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
keyframes: {
|
||||
values: [
|
||||
{
|
||||
interpolation: {
|
||||
type: "EasingFunction",
|
||||
easing_function: "CircOut",
|
||||
},
|
||||
value: 0.0,
|
||||
offset: 0.0,
|
||||
},
|
||||
{
|
||||
interpolation: {
|
||||
type: "Linear",
|
||||
},
|
||||
value: 1.0,
|
||||
offset: 4.0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const EXAMPLE_ANIMATED_ENTITIES: Array<z.input<typeof AnimatedEntity>> =
|
||||
[
|
||||
buildText("Kleine Dumpfkopf!", 1.0, 80, -30, {
|
||||
value: [255, 255, 255, 1.0],
|
||||
}),
|
||||
buildText("Wie gehts?", 1.5, 40, 30, { value: [255, 255, 255, 1.0] }),
|
||||
buildBox(0.6, { value: [30, 30, 30, 1.0] }),
|
||||
buildBox(0.4, { value: [20, 20, 20, 1.0] }),
|
||||
buildBox(0.2, { value: [10, 10, 10, 1.0] }),
|
||||
buildBox(0, { value: [0, 0, 0, 1.0] }),
|
||||
buildStaggeredText("Hallo", 0.0, { value: [30, 30, 30, 1.0] }),
|
||||
buildRect(0, { value: [0, 0, 0, 1.0] }),
|
||||
];
|
||||
|
||||
export const EXAMPLE_ANIMATED_ENTITIES_2: Array<
|
||||
z.input<typeof AnimatedEntity>
|
||||
> = [
|
||||
buildText("Kleine Dumpfkopf!", 1.0, 80, -30, {
|
||||
value: [255, 255, 255, 1.0],
|
||||
}),
|
||||
buildText("Wie gehts?", 1.5, 40, 30, { value: [255, 255, 255, 1.0] }),
|
||||
buildRect(0.6, { value: [30, 30, 30, 1.0] }),
|
||||
buildRect(0.4, { value: [20, 20, 20, 1.0] }),
|
||||
buildRect(0.2, { value: [10, 10, 10, 1.0] }),
|
||||
buildRect(0, { value: [0, 0, 0, 1.0] }),
|
||||
];
|
||||
|
||||
const ExampleTimeline: z.input<typeof Timeline> = {
|
||||
size: [1920, 1080],
|
||||
duration: 10.0,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { BoxEntity, EllipseEntity, TextEntity } from "./Entities";
|
||||
import { EllipseEntity, EntityType, RectEntity, TextEntity } from "./Entities";
|
||||
import { AnimatedVec2 } from "./Values";
|
||||
import { TextPaint } from "./Paint";
|
||||
|
||||
export const AnimationData = z.object({
|
||||
offset: z.number(),
|
||||
@ -8,16 +9,43 @@ export const AnimationData = z.object({
|
||||
visible: z.boolean().optional().default(true),
|
||||
});
|
||||
|
||||
export const AnimatedBoxEntity = BoxEntity.extend({
|
||||
export const AnimatedTransform = z.object({
|
||||
/** Translates by the given animated vec2 */
|
||||
translate: AnimatedVec2,
|
||||
/** Skews by the given animated vec2 */
|
||||
skew: AnimatedVec2,
|
||||
/** Rotates by the given animated vec2 */
|
||||
rotate: AnimatedVec2,
|
||||
/** Scales on the x and y axis by the given animated vec2 */
|
||||
scale: AnimatedVec2,
|
||||
});
|
||||
|
||||
export const AnimatedStaggeredText = z.object({
|
||||
/** Transform applied to the whole layer. */
|
||||
transform: AnimatedTransform,
|
||||
/** The staggered delay that is applied for each letter. Gets multiplied by the index of the letter. */
|
||||
stagger: z.number().min(0),
|
||||
/** These properties get applied to each letter */
|
||||
letter: z.object({
|
||||
transform: AnimatedTransform,
|
||||
paint: TextPaint,
|
||||
}),
|
||||
text: z.string(),
|
||||
animation_data: AnimationData,
|
||||
type: z.literal(EntityType.Enum.StaggeredText),
|
||||
});
|
||||
|
||||
export const AnimatedRectEntity = RectEntity.extend({
|
||||
position: AnimatedVec2,
|
||||
size: AnimatedVec2,
|
||||
origin: AnimatedVec2,
|
||||
|
||||
transform: AnimatedTransform.optional(),
|
||||
animation_data: AnimationData,
|
||||
});
|
||||
|
||||
export const AnimatedTextEntity = TextEntity.extend({
|
||||
origin: AnimatedVec2,
|
||||
transform: AnimatedTransform.optional(),
|
||||
animation_data: AnimationData,
|
||||
});
|
||||
|
||||
@ -25,12 +53,14 @@ export const AnimatedEllipseEntity = EllipseEntity.extend({
|
||||
radius: AnimatedVec2,
|
||||
position: AnimatedVec2,
|
||||
origin: AnimatedVec2,
|
||||
transform: AnimatedTransform.optional(),
|
||||
animation_data: AnimationData,
|
||||
});
|
||||
|
||||
export const AnimatedEntity = z.discriminatedUnion("type", [
|
||||
AnimatedBoxEntity,
|
||||
AnimatedRectEntity,
|
||||
AnimatedTextEntity,
|
||||
AnimatedStaggeredText,
|
||||
AnimatedEllipseEntity,
|
||||
]);
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
import { Vec2 } from "./Values";
|
||||
import { Paint, TextPaint } from "./Paint";
|
||||
|
||||
const EntityTypeOptions = ["Text", "Ellipse", "Box"] as const;
|
||||
const EntityTypeOptions = ["Text", "Ellipse", "Rect", "StaggeredText"] as const;
|
||||
|
||||
export const EntityType = z.enum(EntityTypeOptions);
|
||||
|
||||
@ -10,11 +10,29 @@ export const GeometryEntity = z.object({
|
||||
paint: Paint,
|
||||
});
|
||||
|
||||
export const BoxEntity = GeometryEntity.extend({
|
||||
type: z.literal(EntityType.Enum.Box),
|
||||
export const Transform = z.object({
|
||||
skew: Vec2,
|
||||
rotate: Vec2,
|
||||
translate: Vec2,
|
||||
scale: Vec2,
|
||||
});
|
||||
|
||||
export const StaggeredText = z.object({
|
||||
letter: z.object({
|
||||
position: Vec2,
|
||||
transform: Transform,
|
||||
paint: TextPaint,
|
||||
}),
|
||||
text: z.string(),
|
||||
type: z.literal(EntityType.Enum.StaggeredText),
|
||||
});
|
||||
|
||||
export const RectEntity = GeometryEntity.extend({
|
||||
type: z.literal(EntityType.Enum.Rect),
|
||||
size: Vec2,
|
||||
position: Vec2,
|
||||
origin: Vec2,
|
||||
transform: z.nullable(Transform),
|
||||
});
|
||||
|
||||
export const EllipseEntity = GeometryEntity.extend({
|
||||
@ -22,6 +40,7 @@ export const EllipseEntity = GeometryEntity.extend({
|
||||
radius: Vec2,
|
||||
position: Vec2,
|
||||
origin: Vec2,
|
||||
transform: z.nullable(Transform),
|
||||
});
|
||||
|
||||
export const TextEntity = z.object({
|
||||
@ -29,10 +48,11 @@ export const TextEntity = z.object({
|
||||
paint: TextPaint,
|
||||
origin: Vec2,
|
||||
text: z.string(),
|
||||
transform: z.nullable(Transform),
|
||||
});
|
||||
|
||||
export const Entity = z.discriminatedUnion("type", [
|
||||
BoxEntity,
|
||||
RectEntity,
|
||||
EllipseEntity,
|
||||
TextEntity,
|
||||
]);
|
||||
|
5324
app/yarn.lock
5324
app/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user