use crate::composite::composite_frames_over;
use crate::geometry::Vec2;
use crate::raster::{RasterImage, RasterResidency, Resolution};
use crate::render_context::RenderContext;
use crate::time::{LocalTime, Time};
use crate::timeline_component::{
peel_source, Arrangement, AudioBlockMut, AudioRenderContext, Clock, Cue, NodeKind, Placed,
ResolveCtx, SourceLoc, TimelineComponent,
};
#[crate::component(timeline)]
#[derive(Clone, 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 ChildView<'_> {
fn may_contribute_at(&self, t: f64) -> bool {
match self {
Self::Fill(..) | Self::Bare(..) => true,
Self::PlacedAt(placed, _) => {
let placement = placed.placement();
match placement.end() {
Some(end) => t >= placement.start() && t < end,
None => match placed.child().measure() {
Some(duration) => {
t >= placement.start() && t < placement.start() + duration
}
None => true,
},
}
}
}
}
}
impl TimelineComponent for Timeline {
fn measure(&self) -> Option<f64> {
let mut acc: Option<f64> = 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: f64| cur.max(end)));
}
}
acc
}
fn resolve(&self, abs_start: f64, out: &mut ResolveCtx) -> f64 {
let mut length = 0.0_f64;
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 resolved = placed.resolve(abs_start, out);
length = length.max(placed.measure().unwrap_or(resolved));
}
ChildView::Bare(child) => {
saw_non_fill = true;
let resolved = child.resolve(abs_start, out);
length = length.max(child.measure().unwrap_or(resolved));
}
}
}
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.resolve_fill(abs_start, length, out);
}
}
length
}
fn cues(&self, offset: f64) -> 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, _) => {
cues.extend(placed.cues_fill(offset, fill_len));
}
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,
residency: RasterResidency,
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 possible_frames = self
.classify()
.filter(|view| view.may_contribute_at(local_t))
.take(2)
.count();
let gpu_path = possible_frames > 1 && ctx.prefers_gpu() && ctx.gpu_backend().is_some();
let child_residency = if gpu_path {
RasterResidency::Gpu
} else {
residency
};
let mut frames = Vec::new();
for view in self.classify() {
let image = match view {
ChildView::Fill(placed, _) => match length {
Some(fill_length) => {
placed.frame_fill(clock, fill_length, canvas, target, child_residency, ctx)
}
None => placed.frame(clock, canvas, target, child_residency, ctx),
},
ChildView::PlacedAt(placed, _) => {
placed.frame(clock, canvas, target, child_residency, ctx)
}
ChildView::Bare(child) => child.frame(clock, canvas, target, child_residency, ctx),
};
if let Some(img) = image {
frames.push(img);
}
}
composite_frames_over(frames, target, residency, ctx)
}
fn render_audio_block(&self, mut block: AudioBlockMut<'_>, ctx: &mut AudioRenderContext) {
let request = block.request();
let length = self.measure();
if length.is_some_and(|length| !request.may_overlap_local(0.0, length)) {
block.clear();
return;
}
block.clear();
let mut scratch = ctx.take_scratch(request.sample_len());
for view in self.classify() {
match view {
ChildView::Fill(placed, _) => match length {
Some(fill_length) => placed.render_audio_fill(
AudioBlockMut::new(request, &mut scratch),
fill_length,
ctx,
),
None => {
placed.render_audio_block(AudioBlockMut::new(request, &mut scratch), ctx)
}
},
ChildView::PlacedAt(placed, _) => {
placed.render_audio_block(AudioBlockMut::new(request, &mut scratch), ctx)
}
ChildView::Bare(child) => {
child.render_audio_block(AudioBlockMut::new(request, &mut scratch), ctx)
}
}
for (output, contribution) in block.samples_mut().iter_mut().zip(&scratch) {
*output += contribution;
}
}
if let Some(length) = length {
let channels = request.channels() as usize;
for frame in 0..request.frame_count() {
let t = request.time_at(frame);
if t < 0.0 || t >= length {
block.samples_mut()[frame * channels..(frame + 1) * channels].fill(0.0);
}
}
}
ctx.recycle_scratch(scratch);
}
fn arrangement(&self, offset: f64) -> 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.arrangement_fill(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(Clone, crate::Keyable)]
pub struct Sequence {
#[children(each = child)]
pub children: Vec<Box<dyn TimelineComponent + Send>>,
#[builder(default)]
pub spacing: f64,
}
impl Sequence {
fn is_fill(child: &(dyn TimelineComponent + Send)) -> bool {
child
.structural_any()
.downcast_ref::<Placed>()
.is_some_and(Placed::is_fill)
}
fn possible_contributors_at(&self, local_t: f64) -> usize {
let mut cursor = 0.0_f64;
let mut placed_any = false;
let mut count = 0usize;
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 {
count += 1;
if count == 2 {
return count;
}
}
cursor += slot.unwrap_or(0.0);
placed_any = true;
}
count
}
}
impl TimelineComponent for Sequence {
fn measure(&self) -> Option<f64> {
let mut total = 0.0_f64;
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 f64;
Some(total + self.spacing * gaps)
}
fn resolve(&self, abs_start: f64, out: &mut ResolveCtx) -> f64 {
let mut cursor = 0.0_f64;
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 resolved = child.resolve(abs_start + cursor * out.local_scale(), out);
cursor += child.measure().unwrap_or(resolved);
placed_any = true;
}
cursor
}
fn cues(&self, offset: f64) -> Vec<Cue> {
let mut cues = Vec::new();
let mut cursor = 0.0_f64;
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,
residency: RasterResidency,
ctx: &mut dyn RenderContext,
) -> Option<RasterImage> {
let mut cursor = 0.0_f64;
let mut placed_any = false;
let mut frames = Vec::new();
let local_t = clock.local().seconds();
let gpu_path = self.possible_contributors_at(local_t) > 1
&& ctx.prefers_gpu()
&& ctx.gpu_backend().is_some();
let child_residency = if gpu_path {
RasterResidency::Gpu
} else {
residency
};
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, child_residency, ctx) {
frames.push(img);
}
}
cursor += slot.unwrap_or(0.0);
placed_any = true;
}
composite_frames_over(frames, target, residency, ctx)
}
fn render_audio_block(&self, mut block: AudioBlockMut<'_>, ctx: &mut AudioRenderContext) {
let request = block.request();
block.clear();
let mut scratch = ctx.take_scratch(request.sample_len());
let request_latest = (request.frame_count() > 0).then(|| {
request
.time_at(0)
.max(request.time_at(request.frame_count() - 1))
});
let mut cursor = 0.0_f64;
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();
if slot.is_some()
&& request_latest.is_some_and(|request_latest| request_latest < cursor)
{
break;
}
if slot.is_some_and(|len| !request.may_overlap_local(cursor, cursor + len)) {
cursor += slot.unwrap_or(0.0);
placed_any = true;
continue;
}
let child_request = request.shift_local(-cursor);
child.render_audio_block(AudioBlockMut::new(child_request, &mut scratch), ctx);
let channels = request.channels() as usize;
for frame in 0..request.frame_count() {
let parent_t = request.time_at(frame);
let active = match slot {
Some(len) => parent_t >= cursor && parent_t < cursor + len,
None => true,
};
if active {
let base = frame * channels;
for channel in 0..channels {
block.samples_mut()[base + channel] += scratch[base + channel];
}
}
}
cursor += slot.unwrap_or(0.0);
placed_any = true;
}
ctx.recycle_scratch(scratch);
}
fn arrangement(&self, offset: f64) -> Arrangement {
let mut children = Vec::with_capacity(self.children.len());
let mut cursor = 0.0_f64;
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,
}
}
}