diff --git a/crates/gpui/src/timeline/clip.rs b/crates/gpui/src/timeline/clip.rs index 3bc3904e90..e08ea45c12 100644 --- a/crates/gpui/src/timeline/clip.rs +++ b/crates/gpui/src/timeline/clip.rs @@ -196,6 +196,7 @@ pub struct ClipElement { selected: bool, enabled: bool, locked: bool, + multicam: bool, in_transition: Option, out_transition: Option, content: ClipContent, @@ -227,6 +228,7 @@ impl ClipElement { selected: false, enabled: true, locked: false, + multicam: false, in_transition, out_transition, content: ClipContent::None, @@ -253,6 +255,12 @@ impl ClipElement { self } + /// Builder: render as a multi-cam clip (accent border + corner badge). + pub fn multicam(mut self, multicam: bool) -> Self { + self.multicam = multicam; + self + } + /// Builder: what content the decorator should paint inside the body. pub fn content(mut self, content: ClipContent) -> Self { self.content = content; @@ -282,6 +290,7 @@ struct ClipPaint { selected: bool, enabled: bool, locked: bool, + multicam: bool, in_transition: Option, out_transition: Option, content: ClipContent, @@ -297,6 +306,7 @@ impl RenderOnce for ClipElement { selected, enabled, locked, + multicam, in_transition, out_transition, content, @@ -318,6 +328,7 @@ impl RenderOnce for ClipElement { selected, enabled, locked, + multicam, in_transition, out_transition, content, @@ -399,6 +410,33 @@ impl RenderOnce for ClipElement { ClipContent::None => {} } + // Multi-cam clip overlay: a warm accent border plus + // a corner "camera wedge" (muted-saturation gold, + // clearly distinct from the green clip bodies and + // the green selection outline). + if paint.multicam { + window.paint_quad( + outline( + bounds, + hsla(0.13, 0.9, 0.55, 1.0), + BorderStyle::Solid, + ) + .corner_radii(CLIP_CORNER_RADIUS), + ); + // Corner badge: a small triangle in the top-left + // corner, echoing a lens/record indicator. + let badge = px(10.0); + let mut path = PathBuilder::fill(); + path.move_to(point(bounds.left(), bounds.top())); + path.line_to(point(bounds.left() + badge, bounds.top())); + path.line_to(point(bounds.left(), bounds.top() + badge)); + path.close(); + window.paint_path( + path.build().expect("multicam badge path is valid"), + hsla(0.13, 0.9, 0.42, 1.0), + ); + } + // Selection outline on top of everything. if paint.selected { window.paint_quad( diff --git a/crates/gpui/src/timeline/data.rs b/crates/gpui/src/timeline/data.rs index 313bca5ca6..a809702229 100644 --- a/crates/gpui/src/timeline/data.rs +++ b/crates/gpui/src/timeline/data.rs @@ -141,6 +141,16 @@ pub trait ClipData { fn is_enabled(&self) -> bool { true } + + /// Whether the clip is a multi-cam clip (its texture chain contains a + /// multicam node consumers can switch angles on). + /// + /// The widget renders such clips with a distinct multi-cam overlay + /// (accent border + corner badge) so the rows read as switchable; + /// defaults to `false` for data sources without the concept. + fn is_multicam(&self) -> bool { + false + } } /// A single track (row) of the timeline. diff --git a/crates/gpui/src/timeline/timeline_view.rs b/crates/gpui/src/timeline/timeline_view.rs index 941452ea02..d80752ea8d 100644 --- a/crates/gpui/src/timeline/timeline_view.rs +++ b/crates/gpui/src/timeline/timeline_view.rs @@ -1201,6 +1201,7 @@ impl Render for TimelineView { label: clip.label(), color, enabled: clip.is_enabled(), + multicam: clip.is_multicam(), media_in: clip.media_in(), in_transition, out_transition, @@ -1704,6 +1705,7 @@ impl Render for TimelineView { .selected(state.is_selected(clip.id)) .enabled(clip.enabled) .locked(row_locked) + .multicam(clip.multicam) .content(match kind { TrackKind::Video => ClipContent::Thumbnails, TrackKind::Audio => ClipContent::Waveform, @@ -2145,6 +2147,7 @@ struct ClipRenderData { label: SharedString, color: Hsla, enabled: bool, + multicam: bool, media_in: Frame, in_transition: Option, out_transition: Option,