use crate::audio::AudioMix;
use crate::composite::composite_frames_over;
use crate::geometry::Vec2;
use crate::raster::{RasterImage, Resolution};
use crate::render_context::RenderContext;
use crate::time::{LocalTime, Time};
use crate::timeline_component::{
peel_source, Arrangement, AudioBuffer, Clock, Cue, NodeKind, Placed, ResolveCtx, SourceLoc,
TimelineComponent,
};
#[crate::component(timeline)]
#[derive(crate::Keyable)]
pub struct Timeline {
#[children(each = child)]
pub children: Vec<Box<dyn TimelineComponent + Send>>,
}
impl Timeline {
fn classify(&self) -> impl Iterator<Item = ChildView<'_>> {
self.children.iter().map(|child| {
let obj: &(dyn TimelineComponent + Send) = child.as_ref();
let (source, inner) = peel_source(obj);
match inner.as_any().downcast_ref::<Placed>() {
Some(placed) if placed.is_fill() => ChildView::Fill(placed, source),
Some(placed) => ChildView::PlacedAt(placed, source),
None => ChildView::Bare(obj),
}
})
}
}
enum ChildView<'a> {
Fill(&'a Placed, Option<SourceLoc>),
PlacedAt(&'a Placed, Option<SourceLoc>),
Bare(&'a (dyn TimelineComponent + Send)),
}
impl TimelineComponent for Timeline {
fn measure(&self) -> Option<f32> {
let mut acc: Option<f32> = None;
for view in self.classify() {
let m = match view {
ChildView::Fill(..) => continue,
ChildView::PlacedAt(placed, _) => placed.measure(),
ChildView::Bare(child) => child.measure(),
};
if let Some(end) = m {
acc = Some(acc.map_or(end, |cur: f32| cur.max(end)));
}
}
acc
}
fn resolve(&self, abs_start: f32, out: &mut ResolveCtx) -> f32 {
let mut length = 0.0_f32;
let mut saw_non_fill = false;
let mut saw_fill = false;
for view in self.classify() {
match view {
ChildView::Fill(..) => {
saw_fill = true;
}
ChildView::PlacedAt(placed, _) => {
saw_non_fill = true;
let len = placed.resolve(abs_start, out);
length = length.max(placed.placement().start() + len);
}
ChildView::Bare(child) => {
saw_non_fill = true;
let len = child.resolve(abs_start, out);
length = length.max(len);
}
}
}
if saw_fill && !saw_non_fill {
out.warn(
"Timeline has no non-fill child to set its length; \
it collapses to 0.0 (place media or an explicit window)",
);
}
for view in self.classify() {
if let ChildView::Fill(placed, _) = view {
placed.child().resolve(abs_start, out);
}
}
length
}
fn cues(&self, offset: f32) -> Vec<Cue> {
let fill_len = self.measure().unwrap_or(0.0);
let mut cues = Vec::new();
for view in self.classify() {
match view {
ChildView::Fill(placed, _) => {
let mut child_cues = placed.child().cues(offset);
if placed.child().duration().is_none() {
let abs_end = offset + fill_len;
for cue in &mut child_cues {
cue.end = abs_end;
}
}
cues.extend(child_cues);
}
ChildView::PlacedAt(placed, _) => cues.extend(placed.cues(offset)),
ChildView::Bare(child) => cues.extend(child.cues(offset)),
}
}
cues
}
fn frame(
&self,
clock: Clock<'_>,
canvas: Vec2,
target: Resolution,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
let local_t = clock.local().seconds();
let length = clock
.window()
.map(|window| window.width())
.or_else(|| self.measure());
if let Some(length) = length {
if local_t < 0.0 || local_t >= length {
return None;
}
}
let mut frames = Vec::new();
for child in &self.children {
if let Some(img) = child.frame(clock, canvas, target, ctx) {
frames.push(img);
}
}
composite_frames_over(frames, target, ctx)
}
fn samples(&self, clock: Clock<'_>, window: f32) -> Option<AudioBuffer> {
let _ = (clock, window);
None
}
fn mix_into(&self, mix: &mut AudioMix, start_secs: f32, speed: f32) {
for child in &self.children {
child.mix_into(mix, start_secs, speed);
}
}
fn arrangement(&self, offset: f32) -> Arrangement {
let length = self.measure().unwrap_or(0.0);
let mut children = Vec::with_capacity(self.children.len());
for view in self.classify() {
match view {
ChildView::Fill(placed, source) => {
let mut node = placed.child().arrangement(offset);
if placed.child().duration().is_none() {
node.end = offset + length;
}
if node.source.is_none() {
node.source = source;
}
children.push(node);
}
ChildView::PlacedAt(placed, source) => {
let mut node = placed.arrangement(offset);
if node.source.is_none() {
node.source = source;
}
children.push(node);
}
ChildView::Bare(child) => children.push(child.arrangement(offset)),
}
}
Arrangement {
kind: NodeKind::Timeline,
label: String::new(),
name: None,
source: None,
start: offset,
end: offset + length,
trim: None,
triggers: Vec::new(),
children,
}
}
}
#[crate::component(timeline)]
#[derive(crate::Keyable)]
pub struct Sequence {
#[children(each = child)]
pub children: Vec<Box<dyn TimelineComponent + Send>>,
#[builder(default)]
pub spacing: f32,
}
impl Sequence {
fn is_fill(child: &(dyn TimelineComponent + Send)) -> bool {
child
.structural_any()
.downcast_ref::<Placed>()
.is_some_and(Placed::is_fill)
}
}
impl TimelineComponent for Sequence {
fn measure(&self) -> Option<f32> {
let mut total = 0.0_f32;
let mut any = false;
let mut count = 0usize;
for child in &self.children {
if Self::is_fill(child.as_ref()) {
continue;
}
count += 1;
if let Some(len) = child.measure() {
total += len;
any = true;
}
}
if !any {
return None;
}
let gaps = count.saturating_sub(1) as f32;
Some(total + self.spacing * gaps)
}
fn resolve(&self, abs_start: f32, out: &mut ResolveCtx) -> f32 {
let mut cursor = 0.0_f32;
let mut placed_any = false;
for child in &self.children {
if Self::is_fill(child.as_ref()) {
out.error(
".fill() is not allowed inside a Sequence (it has no container \
length to take); use .at(..) here, or .fill() in a parent Timeline",
);
continue;
}
if placed_any {
cursor += self.spacing;
}
let len = child.resolve(abs_start + cursor * out.local_scale(), out);
cursor += len;
placed_any = true;
}
cursor
}
fn cues(&self, offset: f32) -> Vec<Cue> {
let mut cues = Vec::new();
let mut cursor = 0.0_f32;
let mut placed_any = false;
for child in &self.children {
if Self::is_fill(child.as_ref()) {
continue;
}
if placed_any {
cursor += self.spacing;
}
cues.extend(child.cues(offset + cursor));
cursor += child.measure().unwrap_or(0.0);
placed_any = true;
}
cues
}
fn frame(
&self,
clock: Clock<'_>,
canvas: Vec2,
target: Resolution,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
let mut cursor = 0.0_f32;
let mut placed_any = false;
let mut frames = Vec::new();
let local_t = clock.local().seconds();
for child in &self.children {
if Self::is_fill(child.as_ref()) {
continue;
}
if placed_any {
cursor += self.spacing;
}
let slot = child.measure();
if slot.is_some() && local_t < cursor {
break;
}
let active = match slot {
Some(len) => local_t >= cursor && local_t < cursor + len,
None => true,
};
if active {
let child_clock = clock.with_local_window(LocalTime::new(local_t - cursor), slot);
if let Some(img) = child.frame(child_clock, canvas, target, ctx) {
frames.push(img);
}
}
cursor += slot.unwrap_or(0.0);
placed_any = true;
}
composite_frames_over(frames, target, ctx)
}
fn samples(&self, clock: Clock<'_>, window: f32) -> Option<AudioBuffer> {
let _ = (clock, window);
None
}
fn mix_into(&self, mix: &mut AudioMix, start_secs: f32, speed: f32) {
let mut cursor = 0.0_f32;
let mut placed_any = false;
for child in &self.children {
if Self::is_fill(child.as_ref()) {
continue;
}
if placed_any {
cursor += self.spacing;
}
let slot = child.measure();
let child_start = start_secs + cursor;
if let Some(len) = slot {
if child_start + len <= 0.0 {
cursor += len;
placed_any = true;
continue;
}
if child_start >= mix.duration() {
break;
}
}
child.mix_into(mix, child_start, speed);
cursor += slot.unwrap_or(0.0);
placed_any = true;
}
}
fn arrangement(&self, offset: f32) -> Arrangement {
let mut children = Vec::with_capacity(self.children.len());
let mut cursor = 0.0_f32;
let mut placed_any = false;
for child in &self.children {
if Self::is_fill(child.as_ref()) {
continue;
}
if placed_any {
cursor += self.spacing;
}
children.push(child.arrangement(offset + cursor));
cursor += child.measure().unwrap_or(0.0);
placed_any = true;
}
Arrangement {
kind: NodeKind::Sequence,
label: String::new(),
name: None,
source: None,
start: offset,
end: offset + cursor,
trim: None,
triggers: Vec::new(),
children,
}
}
}