Merge pull request #979 from nanox333/codex/fix-958-vector-context-menu

Fix #958: add vector conversion to layer context menus

# Conflicts:
#	crates/ui-egui/src/panels/timeline.rs
This commit is contained in:
Brandon Thomas 2026-10-12 04:21:11 -04:00
commit fe439f6e9c
2 changed files with 21 additions and 6 deletions

View File

@ -1905,7 +1905,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
None => start_rename(&ctx, layer.id.0, &layer.name),
}
}
layer_context_menu(&row_resp, &comp, layer, &selected, &mut actions);
layer_context_menu(&row_resp, &comp, layer, &selected, vector_footage_layer(&app.session.project, layer), &mut actions);
// Switches.
let sws: [(Icon, bool, &str, bool); 8] = [
(Icon::Shy, sw.shy, "shy", true),
@ -2124,7 +2124,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
set_anchor(&ctx, cid, layer.id);
}
}
layer_context_menu(&body, &comp, layer, &selected, &mut actions);
layer_context_menu(&body, &comp, layer, &selected, 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() {
@ -3497,17 +3497,25 @@ fn prop_menu(
/// A layer row's or bar's context menu. As in After Effects, right-clicking a layer that is part
/// of the selection keeps the selection and the menu acts on all of it; right-clicking an
/// unselected layer selects only that layer.
fn layer_context_menu(resp: &egui::Response, comp: &Comp, layer: &Layer, selected: &[LayerId], actions: &mut Vec<(String, serde_json::Value)>) {
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, comp: &Comp, layer: &Layer, selected: &[LayerId], vector: bool, actions: &mut Vec<(String, serde_json::Value)>) {
let in_selection = selected.contains(&layer.id);
if resp.secondary_clicked() && !in_selection && !layer.switches.locked {
actions.push(("layer.select".into(), json!({"layers": [layer.id.0]})));
}
let targets: Vec<u64> = if in_selection { comp.layers.iter().filter(|l| selected.contains(&l.id)).map(|l| l.id.0).collect() } else { vec![layer.id.0] };
crate::widgets::context_menu(&resp, |ui| layer_menu(ui, &targets, actions));
crate::widgets::context_menu(&resp, |ui| layer_menu(ui, &targets, 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() {
@ -3526,6 +3534,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}));

View File

@ -1219,7 +1219,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);
}
ui.separator();
if ui.button(copy_frame).clicked() {