From e0a23e876381143a1872069fe7593dc6d297bf60 Mon Sep 17 00:00:00 2001 From: Kristian Myram Date: Mon, 12 Oct 2026 08:01:52 +0200 Subject: [PATCH] Fix #958: add vector conversion to layer context menus --- crates/ui-egui/src/panels/timeline.rs | 21 ++++++++++++++++----- crates/ui-egui/src/panels/viewer.rs | 6 +++++- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/panels/timeline.rs b/crates/ui-egui/src/panels/timeline.rs index 61b2d22d..57b2830c 100644 --- a/crates/ui-egui/src/panels/timeline.rs +++ b/crates/ui-egui/src/panels/timeline.rs @@ -1879,7 +1879,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { _ => start_rename(&ctx, layer.id.0, &layer.name), } } - layer_context_menu(&row_resp, layer, &mut actions); + layer_context_menu(&row_resp, layer, vector_footage_layer(&app.session.project, layer), &mut actions); // Switches. let sws: [(Icon, bool, &str, bool); 8] = [ (Icon::Shy, sw.shy, "shy", true), @@ -2097,7 +2097,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { set_anchor(&ctx, cid, layer.id); } } - layer_context_menu(&body, layer, &mut actions); + layer_context_menu(&body, layer, vector_footage_layer(&app.session.project, layer), &mut actions); let drag_key = format!("bar-{}", layer.id.0); for (resp, kind) in [(&body, "move"), (&lin, "in"), (&lout, "out")] { if resp.dragged() { @@ -3442,12 +3442,20 @@ fn prop_menu( item(ui, "essentialGraphics", "Add Property to Essential Graphics", false, "essential.addProperty", json!({"comp": comp.0, "layer": l, "prop": uid})); } -fn layer_context_menu(resp: &egui::Response, layer: &Layer, actions: &mut Vec<(String, serde_json::Value)>) { - resp.context_menu(|ui| layer_menu(ui, &[layer.id.0], actions)); +pub(crate) fn vector_footage_layer(project: &effectcraft_engine::project::Project, layer: &Layer) -> bool { + layer + .source + .item() + .and_then(|item| project.item(item)) + .is_some_and(|item| matches!(&item.kind, effectcraft_engine::project::ItemKind::Footage(footage) if effectcraft_engine::vector::is_vector(footage))) +} + +fn layer_context_menu(resp: &egui::Response, layer: &Layer, vector: bool, actions: &mut Vec<(String, serde_json::Value)>) { + resp.context_menu(|ui| layer_menu(ui, &[layer.id.0], vector, actions)); } /// Shared layer actions for Timeline rows and Composition context menus. -pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(String, serde_json::Value)>) { +pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], vector: bool, actions: &mut Vec<(String, serde_json::Value)>) { let Some(&id) = layers.first() else { return }; let mut item = |ui: &mut egui::Ui, label: &str, cmd: &str, params: serde_json::Value| { if ui.button(label).clicked() { @@ -3466,6 +3474,9 @@ pub(crate) fn layer_menu(ui: &mut egui::Ui, layers: &[u64], actions: &mut Vec<(S item(ui, "Adjustment Layer", "layer.newAdjustment", json!({})); }); item(ui, "Layer Settings…", "layer.settings", json!({})); + if vector { + item(ui, "Create Shapes from Vector Layer", "layer.create", json!({"op": "shapesFromVector", "layers": layers})); + } ui.separator(); ui.menu_button(crate::i18n::tr("Masks"), |ui| { item(ui, "New Mask", "layer.addMask", json!({"layer": id})); diff --git a/crates/ui-egui/src/panels/viewer.rs b/crates/ui-egui/src/panels/viewer.rs index 5fe7697c..7b43be16 100644 --- a/crates/ui-egui/src/panels/viewer.rs +++ b/crates/ui-egui/src/panels/viewer.rs @@ -1131,7 +1131,11 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { if context_layers.is_empty() { ui.label(crate::i18n::tr("No unlocked layer selected")); } else { - super::timeline::layer_menu(ui, &context_layers, &mut context_actions); + let vector = context_layers + .iter() + .filter_map(|id| comp.layer(effectcraft_engine::project::LayerId(*id))) + .any(|layer| super::timeline::vector_footage_layer(&app.session.project, layer)); + super::timeline::layer_menu(ui, &context_layers, vector, &mut context_actions); } }); for (id, params) in context_actions {