///|
pub(all) enum FillMode {
None
Forwards
Backwards
Both
Auto
} derive(Debug, Eq)
///|
pub impl Show for FillMode with fn output(self, logger) {
match self {
FillMode::None => logger.write_string("None")
FillMode::Forwards => logger.write_string("Forwards")
FillMode::Backwards => logger.write_string("Backwards")
FillMode::Both => logger.write_string("Both")
FillMode::Auto => logger.write_string("Auto")
}
}
///|
pub(all) enum PlaybackDirection {
Normal
Reverse
Alternate
AlternateReverse
} derive(Debug, Eq)
///|
pub impl Show for PlaybackDirection with fn output(self, logger) {
match self {
PlaybackDirection::Normal => logger.write_string("Normal")
PlaybackDirection::Reverse => logger.write_string("Reverse")
PlaybackDirection::Alternate => logger.write_string("Alternate")
PlaybackDirection::AlternateReverse =>
logger.write_string("AlternateReverse")
}
}
///|
pub(all) enum AnimationPhase {
Before
Active
After
} derive(Debug, Eq)
///|
pub impl Show for AnimationPhase with fn output(self, logger) {
match self {
AnimationPhase::Before => logger.write_string("Before")
AnimationPhase::Active => logger.write_string("Active")
AnimationPhase::After => logger.write_string("After")
}
}
///|
pub(all) enum CompositeOperation {
Replace
Add
Accumulate
} derive(Debug, Eq)
///|
pub impl Show for CompositeOperation with fn output(self, logger) {
match self {
CompositeOperation::Replace => logger.write_string("Replace")
CompositeOperation::Add => logger.write_string("Add")
CompositeOperation::Accumulate => logger.write_string("Accumulate")
}
}
///|
pub(all) enum IterationCompositeOperation {
Replace
Accumulate
} derive(Debug, Eq)
///|
pub impl Show for IterationCompositeOperation with fn output(self, logger) {
match self {
IterationCompositeOperation::Replace => logger.write_string("Replace")
IterationCompositeOperation::Accumulate => logger.write_string("Accumulate")
}
}
///|
pub(all) struct EffectTiming {
delay : Double
end_delay : Double
fill : FillMode
iteration_start : Double
iterations : Double
duration : Double
direction : PlaybackDirection
easing : @values.Easing
} derive(Debug, Eq)
///|
pub(all) struct ComputedEffectTiming {
phase : AnimationPhase
active_duration : Double
end_time : Double
local_time : Double
active_time : Double?
progress : Double?
current_iteration : Int?
directed_progress : Double?
transformed_progress : Double?
} derive(Debug, Eq)
///|
pub(all) struct NumericKeyframe {
offset : Double
value : Double
easing : @values.Easing
composite : CompositeOperation?
} derive(Debug, Eq)
///|
pub(all) struct NumericKeyframeEffect {
keyframes : Array[NumericKeyframe]
timing : EffectTiming
composite : CompositeOperation
iteration_composite : IterationCompositeOperation
} derive(Debug, Eq)
///|
fn max_double(a : Double, b : Double) -> Double {
if a > b {
a
} else {
b
}
}
///|
fn fill_allows_before(fill : FillMode) -> Bool {
match fill {
FillMode::Backwards | FillMode::Both => true
FillMode::None | FillMode::Forwards | FillMode::Auto => false
}
}
///|
fn fill_allows_after(fill : FillMode) -> Bool {
match fill {
FillMode::Forwards | FillMode::Both => true
FillMode::None | FillMode::Backwards | FillMode::Auto => false
}
}
///|
fn is_reverse_iteration(
direction : PlaybackDirection,
current_iteration : Int,
) -> Bool {
match direction {
PlaybackDirection::Normal => false
PlaybackDirection::Reverse => true
PlaybackDirection::Alternate => current_iteration % 2 == 1
PlaybackDirection::AlternateReverse => current_iteration % 2 == 0
}
}
///|
fn clamp_unit(value : Double) -> Double {
if value < 0.0 {
0.0
} else if value > 1.0 {
1.0
} else {
value
}
}
///|
fn compute_progress(
timing : EffectTiming,
phase : AnimationPhase,
active_time : Double,
active_duration : Double,
) -> (Double, Int, Double) {
if active_duration <= 0.0 || timing.duration <= 0.0 {
let progress = if phase == AnimationPhase::Before { 0.0 } else { 1.0 }
let current_iteration = 0
let directed = if is_reverse_iteration(timing.direction, current_iteration) {
1.0 - progress
} else {
progress
}
return (progress, current_iteration, directed)
}
let overall_progress = active_time / timing.duration + timing.iteration_start
let at_end = active_time == active_duration && timing.iterations > 0.0
let current_iteration = if at_end {
max_double(0.0, overall_progress.ceil() - 1.0).to_int()
} else {
max_double(0.0, overall_progress.floor()).to_int()
}
let progress = if at_end {
1.0
} else {
overall_progress - overall_progress.floor()
}
let progress = clamp_unit(progress)
let directed = if is_reverse_iteration(timing.direction, current_iteration) {
1.0 - progress
} else {
progress
}
(progress, current_iteration, directed)
}
///|
pub fn EffectTiming::new(duration : Double) -> EffectTiming {
{
delay: 0.0,
end_delay: 0.0,
fill: FillMode::Auto,
iteration_start: 0.0,
iterations: 1.0,
duration,
direction: PlaybackDirection::Normal,
easing: @values.Easing::Linear,
}
}
///|
pub fn EffectTiming::active_duration(self : EffectTiming) -> Double {
max_double(0.0, self.duration) * max_double(0.0, self.iterations)
}
///|
pub fn EffectTiming::end_time(self : EffectTiming) -> Double {
self.delay + self.active_duration() + self.end_delay
}
///|
pub fn EffectTiming::at(
self : EffectTiming,
local_time : Double,
) -> ComputedEffectTiming {
let active_duration = self.active_duration()
let active_start = self.delay
let active_end = active_start + active_duration
let phase = if local_time < active_start {
AnimationPhase::Before
} else if local_time < active_end {
AnimationPhase::Active
} else {
AnimationPhase::After
}
let active_time = match phase {
AnimationPhase::Before =>
if fill_allows_before(self.fill) {
Some(0.0)
} else {
None
}
AnimationPhase::Active => Some(local_time - active_start)
AnimationPhase::After =>
if fill_allows_after(self.fill) {
Some(active_duration)
} else {
None
}
}
match active_time {
Some(active_time) => {
let (progress, current_iteration, directed_progress) = compute_progress(
self, phase, active_time, active_duration,
)
{
phase,
active_duration,
end_time: self.end_time(),
local_time,
active_time: Some(active_time),
progress: Some(progress),
current_iteration: Some(current_iteration),
directed_progress: Some(directed_progress),
transformed_progress: Some(self.easing.sample(directed_progress)),
}
}
None =>
{
phase,
active_duration,
end_time: self.end_time(),
local_time,
active_time: None,
progress: None,
current_iteration: None,
directed_progress: None,
transformed_progress: None,
}
}
}
///|
pub fn NumericKeyframe::new(offset : Double, value : Double) -> NumericKeyframe {
{ offset, value, easing: @values.Easing::Linear, composite: None }
}
///|
pub fn NumericKeyframe::with_easing(
offset : Double,
value : Double,
easing : @values.Easing,
) -> NumericKeyframe {
{ offset, value, easing, composite: None }
}
///|
pub fn NumericKeyframe::with_composite(
offset : Double,
value : Double,
easing : @values.Easing,
composite : CompositeOperation,
) -> NumericKeyframe {
{ offset, value, easing, composite: Some(composite) }
}
///|
fn validate_numeric_keyframes(keyframes : Array[NumericKeyframe]) -> Bool {
if keyframes.length() < 2 {
return false
}
if keyframes[0].offset != 0.0 {
return false
}
if keyframes[keyframes.length() - 1].offset != 1.0 {
return false
}
for i = 0; i < keyframes.length(); i = i + 1 {
if keyframes[i].offset < 0.0 || keyframes[i].offset > 1.0 {
return false
}
if i > 0 && keyframes[i].offset <= keyframes[i - 1].offset {
return false
}
}
true
}
///|
fn keyframe_composite(
effect : NumericKeyframeEffect,
keyframe : NumericKeyframe,
) -> CompositeOperation {
match keyframe.composite {
Some(composite) => composite
None => effect.composite
}
}
///|
fn apply_composite(
composite : CompositeOperation,
underlying_value : Double,
effect_value : Double,
) -> Double {
match composite {
CompositeOperation::Replace => effect_value
CompositeOperation::Add | CompositeOperation::Accumulate =>
underlying_value + effect_value
}
}
///|
fn iteration_accumulation_delta(effect : NumericKeyframeEffect) -> Double {
let keyframes = effect.keyframes
keyframes[keyframes.length() - 1].value - keyframes[0].value
}
///|
pub fn NumericKeyframeEffect::new(
keyframes : Array[NumericKeyframe],
timing : EffectTiming,
) -> NumericKeyframeEffect? {
if !validate_numeric_keyframes(keyframes) {
return None
}
Some({
keyframes: keyframes.copy(),
timing,
composite: CompositeOperation::Replace,
iteration_composite: IterationCompositeOperation::Replace,
})
}
///|
pub fn NumericKeyframeEffect::with_composite(
keyframes : Array[NumericKeyframe],
timing : EffectTiming,
composite : CompositeOperation,
iteration_composite : IterationCompositeOperation,
) -> NumericKeyframeEffect? {
if !validate_numeric_keyframes(keyframes) {
return None
}
Some({ keyframes: keyframes.copy(), timing, composite, iteration_composite })
}
///|
pub fn NumericKeyframeEffect::sample_progress(
self : NumericKeyframeEffect,
progress : Double,
) -> Double {
self.sample_progress_with_underlying(progress, 0.0, 0)
}
///|
pub fn NumericKeyframeEffect::sample_progress_with_underlying(
self : NumericKeyframeEffect,
progress : Double,
underlying_value : Double,
current_iteration : Int,
) -> Double {
let progress = clamp_unit(progress)
let keyframes = self.keyframes
let iteration_accumulation = match self.iteration_composite {
IterationCompositeOperation::Replace => 0.0
IterationCompositeOperation::Accumulate =>
current_iteration.to_double() * iteration_accumulation_delta(self)
}
if progress <= keyframes[0].offset {
return apply_composite(
keyframe_composite(self, keyframes[0]),
underlying_value,
keyframes[0].value + iteration_accumulation,
)
}
let last_index = keyframes.length() - 1
if progress >= keyframes[last_index].offset {
return apply_composite(
keyframe_composite(self, keyframes[last_index]),
underlying_value,
keyframes[last_index].value + iteration_accumulation,
)
}
for i = 0; i < last_index; i = i + 1 {
let start = keyframes[i]
let end = keyframes[i + 1]
if progress <= end.offset {
let local_progress = (progress - start.offset) /
(end.offset - start.offset)
let eased_progress = start.easing.sample(local_progress)
let start_value = apply_composite(
keyframe_composite(self, start),
underlying_value,
start.value + iteration_accumulation,
)
let end_value = apply_composite(
keyframe_composite(self, end),
underlying_value,
end.value + iteration_accumulation,
)
return start_value + (end_value - start_value) * eased_progress
}
}
apply_composite(
keyframe_composite(self, keyframes[last_index]),
underlying_value,
keyframes[last_index].value + iteration_accumulation,
)
}
///|
pub fn NumericKeyframeEffect::sample(
self : NumericKeyframeEffect,
local_time : Double,
) -> Double? {
self.sample_with_underlying(local_time, 0.0)
}
///|
pub fn NumericKeyframeEffect::sample_with_underlying(
self : NumericKeyframeEffect,
local_time : Double,
underlying_value : Double,
) -> Double? {
let timing = self.timing.at(local_time)
match (timing.transformed_progress, timing.current_iteration) {
(Some(progress), Some(current_iteration)) =>
Some(
self.sample_progress_with_underlying(
progress, underlying_value, current_iteration,
),
)
_ => None
}
}