timeline: multicam clip special overlay (accent border + corner badge), ClipData::is_multicam flag

This commit is contained in:
2026-09-01 20:27:49 +08:00
parent 41dac8f33e
commit 209c593da6
3 changed files with 51 additions and 0 deletions
+38
View File
@@ -196,6 +196,7 @@ pub struct ClipElement {
selected: bool,
enabled: bool,
locked: bool,
multicam: bool,
in_transition: Option<FrameRange>,
out_transition: Option<FrameRange>,
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<FrameRange>,
out_transition: Option<FrameRange>,
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(
+10
View File
@@ -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.
@@ -1201,6 +1201,7 @@ impl<D: TimelineDataSource> Render for TimelineView<D> {
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<D: TimelineDataSource> Render for TimelineView<D> {
.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<FrameRange>,
out_transition: Option<FrameRange>,