Viewer: no proxy notice on a collapsed precomp, which draws its own layers (#1701)

This commit is contained in:
tylerjenningsw
2026-10-18 12:59:24 -04:00
parent d2ae0bd503
commit f52ffb63ce
2 changed files with 41 additions and 13 deletions
+16 -12
View File
@@ -8,8 +8,8 @@ use effectcraft_engine::commands::shape_tool::PaintKind;
use effectcraft_engine::effects::puppet::PinKind;
use effectcraft_engine::geom::{Mat3, vec2 as gv2};
use effectcraft_engine::project::{Comp, Layer, LayerId, LayerSource};
use effectcraft_engine::render::EvalCtx;
use effectcraft_engine::render::three_d::{self, CameraState, View3D};
use effectcraft_engine::render::{EvalCtx, Renderer};
use effectcraft_engine::time::Tick;
use egui::{Align2, Color32, Pos2, Rect, Sense, Stroke, StrokeKind, pos2, vec2};
use serde_json::json;
@@ -1194,16 +1194,21 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
}
// Footage (or the proxy in use for footage or a precomp) that can't be decoded (no decoder
// for its codec / profile): say why on the layer instead of leaving a black picture.
let mut notices = 0;
for layer in comp.layers.iter().filter(|l| l.is_active_at(time) && l.switches.video) {
let (effectcraft_engine::project::LayerSource::Footage { item } | effectcraft_engine::project::LayerSource::Comp { item }) = &layer.source else {
continue;
};
let Some(why) = app.session.project.item(*item).and_then(|i| app.session.footage.item_problem(i)) else { continue };
if notices >= 3 {
break;
}
// for its codec / profile): say why on the layer instead of leaving a black picture. A
// collapsed precomp draws its own layers, never the comp's proxy.
let problems: Vec<(&Layer, String)> = {
let r = Renderer::new(&snap_project, &*app.session.footage, Default::default());
comp.layers
.iter()
.filter(|l| l.is_active_at(time) && l.switches.video && r.collapsed(&ectx, l).is_none())
.filter_map(|layer| {
let (LayerSource::Footage { item } | LayerSource::Comp { item }) = &layer.source else { return None };
Some((layer, snap_project.item(*item).and_then(|i| app.session.footage.item_problem(i))?))
})
.take(3)
.collect()
};
for (notices, (layer, why)) in problems.into_iter().enumerate() {
let (m, _) = l2c(&ectx, layer);
let (w, h) = effectcraft_engine::render::source_size(&app.session.project, layer);
let c = m.apply(effectcraft_engine::geom::vec2(w as f64 / 2.0, h as f64 / 2.0));
@@ -1215,7 +1220,6 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
painter.rect_stroke(r, 3.0, Stroke::new(1.0, t.warning), StrokeKind::Inside);
painter.galley(pos2(r.min.x + 12.0, r.center().y - galley.size().y / 2.0), galley, Color32::WHITE);
app.auto.add(&format!("viewer.footageProblem.{}", layer.id.0), r, &text);
notices += 1;
}
// Free transform box of a path (double-click a path with the Selection tool).
+25 -1
View File
@@ -45,10 +45,14 @@ fn harness(broken: fn(&Footage) -> bool, proxy: Option<Footage>) -> (Harness<'st
item.proxy = Some(Box::new(Proxy { footage, enabled: true }));
}
s.execute("layer.addItem", json!({"item": clip.0})).unwrap();
(open(s), clip)
}
fn open(mut s: Session) -> Harness<'static, EffectcraftApp> {
s.execute("project.select", json!({"items": []})).unwrap();
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(s));
h.run_steps(4);
(h, clip)
h
}
#[test]
@@ -78,3 +82,23 @@ fn an_undecodable_proxy_in_use_is_marked_as_the_proxy() {
let notice = h.state().auto.query("viewer.footageProblem.").first().copied().expect("a notice in the viewer").clone();
assert!(notice.label.contains(&format!("Proxy: {WHY}")), "{notice:?}");
}
/// A precomp whose proxy can't be decoded: the layer says so while it renders through the
/// proxy, but not with Collapse Transformations on, which draws the precomp's own layers.
#[test]
fn a_collapsed_precomp_has_no_notice_for_its_proxy() {
for collapse in [false, true] {
let mut s = Session { footage: Arc::new(Stub(|f| f.path.ends_with(".avi"))), ..Default::default() };
s.execute("comp.new", json!({"name": "Inner", "width": 64, "height": 36, "duration": 4})).unwrap();
s.execute("comp.new", json!({"name": "Main", "width": 320, "height": 180, "duration": 4})).unwrap();
let p = Arc::make_mut(&mut s.project);
let inner = p.items.values_mut().find(|i| i.name == "Inner").unwrap();
let proxy = Footage { kind: FootageKind::Video, path: "inner_proxy.avi".into(), width: 64, height: 36, has_video: true, ..Default::default() };
inner.proxy = Some(Box::new(Proxy { footage: proxy, enabled: true }));
let inner = inner.id;
s.execute("layer.addItem", json!({"item": inner.0})).unwrap();
s.execute("layer.setSwitch", json!({"layers": ["#1"], "switch": "collapse", "value": collapse})).unwrap();
let h = open(s);
assert_eq!(h.state().auto.query("viewer.footageProblem.").is_empty(), collapse, "collapse {collapse}");
}
}