summaryrefslogtreecommitdiff
path: root/rust/crates/timeline/src/split.rs
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
committercashmere <cashmere@autistici.org>2026-07-28 19:41:16 +0200
commitf5a82152394fde4d8f740f664ab7737b82861618 (patch)
tree5a51b181abead1ce03d2dbfc12b77918d3be0e1b /rust/crates/timeline/src/split.rs
parente837e7504379e100ff3b56282577ec67bb88bec9 (diff)
downloadopencut-f5a82152394fde4d8f740f664ab7737b82861618.tar.gz
feat: GPUI desktop port foundation
- scene crate: full project/scene/element model, serde round-trip proven against the web SerializedProject shape - transfer crate: .ocp zip container, cross-compat with web fflate archives proven in both directions - storage crate: filesystem project store replacing IndexedDB, .ocp import/export - media crate: ffmpeg-next 8 decoder (frames, audio, thumbnails, waveform summaries) - playback crate: wall-clock anchored, frame-rounded playback clock - gpu crate: native texture readback; compositor renders headless offscreen on Vulkan (spike test) - flake devshell: rust toolchain, GPUI native deps, ffmpeg dev, bindgen clang args; GPUI hello-world builds - port plan in notes/gpui-port.md
Diffstat (limited to 'rust/crates/timeline/src/split.rs')
-rw-r--r--rust/crates/timeline/src/split.rs143
1 files changed, 143 insertions, 0 deletions
diff --git a/rust/crates/timeline/src/split.rs b/rust/crates/timeline/src/split.rs
new file mode 100644
index 00000000..5e7332b1
--- /dev/null
+++ b/rust/crates/timeline/src/split.rs
@@ -0,0 +1,143 @@
+use scene::{Element, ElementRef, SceneTracks};
+use time::MediaTime;
+
+use crate::retime::{get_source_span_at_clip_time, round_media_time};
+
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum RetainSide {
+ Both,
+ Left,
+ Right,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct SplitElementsResult {
+ pub tracks: SceneTracks,
+ pub right_side_elements: Vec<ElementRef>,
+}
+
+pub fn split_elements(
+ tracks: &SceneTracks,
+ targets: &[ElementRef],
+ split_time: MediaTime,
+ retain_side: RetainSide,
+) -> SplitElementsResult {
+ let mut right_side_elements = Vec::new();
+
+ let split_track = |track: &scene::Track, right_side_elements: &mut Vec<ElementRef>| {
+ let has_targets = targets.iter().any(|target| target.track_id == track.id());
+ if !has_targets {
+ return track.clone();
+ }
+
+ let elements = track
+ .elements()
+ .iter()
+ .flat_map(|element| {
+ let should_split = targets
+ .iter()
+ .any(|target| target.element_id == element.base().id);
+ if !should_split {
+ return vec![element.clone()];
+ }
+ split_element(track.id(), element, split_time, retain_side, right_side_elements)
+ })
+ .collect();
+
+ let mut next = track.clone();
+ *next.elements_mut() = elements;
+ next
+ };
+
+ let updated = SceneTracks {
+ overlay: tracks
+ .overlay
+ .iter()
+ .map(|track| split_track(track, &mut right_side_elements))
+ .collect(),
+ main: split_track(&tracks.main, &mut right_side_elements),
+ audio: tracks
+ .audio
+ .iter()
+ .map(|track| split_track(track, &mut right_side_elements))
+ .collect(),
+ };
+
+ SplitElementsResult {
+ tracks: updated,
+ right_side_elements,
+ }
+}
+
+fn split_element(
+ track_id: &str,
+ element: &Element,
+ split_time: MediaTime,
+ retain_side: RetainSide,
+ right_side_elements: &mut Vec<ElementRef>,
+) -> Vec<Element> {
+ let base = element.base();
+ let effective_start = base.start_time;
+ let effective_end = base.start_time + base.duration;
+
+ if split_time <= effective_start || split_time >= effective_end {
+ return vec![element.clone()];
+ }
+
+ let relative_time = split_time - base.start_time;
+ let left_visible_duration = relative_time;
+ let right_visible_duration = base.duration - relative_time;
+ let retime = crate::element_retime(element);
+
+ let left_source_span = round_media_time(get_source_span_at_clip_time(
+ left_visible_duration.as_ticks() as f64,
+ retime.as_ref(),
+ ));
+ let total_source_span = round_media_time(get_source_span_at_clip_time(
+ base.duration.as_ticks() as f64,
+ retime.as_ref(),
+ ));
+ let right_source_span = total_source_span - left_source_span;
+
+ let (left_animations, right_animations) = animation::split_animations_at_time_with_options(
+ base.animations.as_ref(),
+ relative_time,
+ true,
+ );
+
+ let left_trim_end = base.trim_end + right_source_span;
+ let right_trim_start = base.trim_start + left_source_span;
+
+ let build_left = || {
+ let mut left = element.clone();
+ let left_base = left.base_mut();
+ left_base.duration = left_visible_duration;
+ left_base.trim_end = left_trim_end;
+ left_base.name = format!("{} (left)", left_base.name);
+ left_base.animations = left_animations;
+ left
+ };
+
+ let build_right = |right_side_elements: &mut Vec<ElementRef>| {
+ let new_id = animation::generate_uuid();
+ right_side_elements.push(ElementRef {
+ track_id: track_id.to_string(),
+ element_id: new_id.clone(),
+ });
+ let mut right = element.clone();
+ let right_base = right.base_mut();
+ right_base.id = new_id;
+ right_base.start_time = split_time;
+ right_base.duration = right_visible_duration;
+ right_base.trim_start = right_trim_start;
+ right_base.name = format!("{} (right)", right_base.name);
+ right_base.animations = right_animations;
+ right
+ };
+
+ match retain_side {
+ RetainSide::Left => vec![build_left()],
+ RetainSide::Right => vec![build_right(right_side_elements)],
+ RetainSide::Both => vec![build_left(), build_right(right_side_elements)],
+ }
+}