diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index f561bb229..e43cbeafc 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -3504,13 +3504,16 @@ fn font_items(english_names: bool) -> Vec { /// The raster effects' submenus of the Effect menu (the Photoshop-style effects, below the /// vector effects): (submenu, the catalogue's menu path of its effects). -const RASTER_MENUS: [(&str, &[&str]); 8] = [ +/// The raster Stylize submenu shares its path with the vector one and lists only the effects at +/// that path that [`vectorcraft_effects::PIXEL_EFFECTS`] names. +const RASTER_MENUS: [(&str, &[&str]); 9] = [ ("Artistic", &["Effect", "Artistic"]), ("Blur", &["Effect", "Blur"]), ("Brush Strokes", &["Effect", "Brush Strokes"]), ("Distort", &["Effect", "Distort"]), ("Pixelate", &["Effect", "Pixelate"]), ("Sharpen", &["Effect", "Sharpen"]), + ("Stylize", &["Effect", "Stylize"]), ("Texture", &["Effect", "Texture"]), ("Video", &["Effect", "Video"]), ]; @@ -3540,10 +3543,16 @@ fn effect_menu() -> Vec { "Warp", ]; let top_level = |e: &vectorcraft_effects::EffectInfo| e.menu == ["Effect"] && order.contains(&e.label); + // An effect goes in the vector submenu its path names unless it is a Photoshop-style effect, + // which goes in the raster submenu of the same name when `RASTER_MENUS` has one (Stylize › + // Glowing Edges). + let in_vector_menu = + |e: &vectorcraft_effects::EffectInfo| order.iter().any(|s| e.menu == ["Effect", *s]) && !vectorcraft_effects::PIXEL_EFFECTS.contains(&e.id); // The effects whose catalogue menu path is `path`. - let items_at = |path: &[&str]| -> Vec { + // `vector` picks the ones for the vector submenu or the ones for the raster submenu. + let items_at = |path: &[&str], vector: bool| -> Vec { cat.iter() - .filter(|e| e.menu == path) + .filter(|e| e.menu == path && in_vector_menu(e) == vector) .map(|e| match e.defaults.as_object().is_some_and(|o| o.is_empty()) { // No options (Effect → Pathfinder): apply directly, like Illustrator. true => Item::Cmd(e.label, "effect.apply", json!({ "effect": e.id })), @@ -3556,7 +3565,7 @@ fn effect_menu() -> Vec { out.push(Item::Cmd(e.label, "effect.apply", json!({ "effect": e.id }))); continue; } - let items = items_at(&["Effect", sub_name]); + let items = items_at(&["Effect", sub_name], true); if items.is_empty() { let placeholder = match sub_name { "3D and Materials" => vec![todo("Extrude & Bevel…"), todo("Revolve…"), todo("Inflate…"), todo("Rotate…"), todo("Materials…")], @@ -3583,7 +3592,7 @@ fn effect_menu() -> Vec { let raster: Vec = RASTER_MENUS .iter() .filter_map(|(name, path)| { - let items = items_at(path); + let items = items_at(path, false); (!items.is_empty()).then(|| sub(name, items)) }) .collect(); @@ -3593,9 +3602,7 @@ fn effect_menu() -> Vec { out.extend(raster); } // Anything not placed above (future effects) still shows up. - let placed = |e: &vectorcraft_effects::EffectInfo| { - top_level(e) || order.iter().any(|s| e.menu == ["Effect", *s]) || RASTER_MENUS.iter().any(|(_, path)| e.menu == *path) - }; + let placed = |e: &vectorcraft_effects::EffectInfo| top_level(e) || in_vector_menu(e) || RASTER_MENUS.iter().any(|(_, path)| e.menu == *path); for e in cat.iter().filter(|e| !placed(e)) { out.push(Item::Cmd(e.label, "effect.dialog", json!({ "effect": e.id }))); } diff --git a/crates/ui-egui/src/tests_labels.rs b/crates/ui-egui/src/tests_labels.rs index 5f9e9b4c9..e4c1c3303 100644 --- a/crates/ui-egui/src/tests_labels.rs +++ b/crates/ui-egui/src/tests_labels.rs @@ -140,6 +140,40 @@ fn effect_menu_lists_the_texture_filters_between_sharpen_and_video() { assert!(at("Sharpen") < at("Texture") && at("Texture") < at("Video")); } +/// Effect › Stylize under Raster Effects, between Sharpen and Texture: Glowing Edges opens its +/// dialog. +#[test] +fn effect_menu_lists_glowing_edges_in_the_raster_stylize_between_sharpen_and_texture() { + let app = app(); + let entries = menus::menu_entries(&app); + let at = |label: &str| entries.iter().position(|e| e.path[0] == "Effect" && e.label == label).unwrap(); + let glowing = &entries[at("Glowing Edges…")]; + assert_eq!(glowing.path, ["Effect", "Stylize"]); + assert_eq!(glowing.command.as_deref(), Some("effect.dialog")); + assert_eq!(glowing.params, json!({"effect": "stylize.glowingEdges"})); + assert!(at("Unsharp Mask…") < at("Glowing Edges…") && at("Glowing Edges…") < at("Craquelure…")); +} + +/// The vector effects' Stylize keeps its six effects and leaves Glowing Edges out. +#[test] +fn effect_menu_keeps_glowing_edges_out_of_the_vector_stylize() { + let tree = menus::menu_tree(); + let (_, effect) = tree.iter().find(|(t, _)| *t == "Effect").expect("Effect menu"); + let raster = effect.iter().position(|i| matches!(i, Item::Header("Raster Effects"))).expect("Raster Effects"); + let stylize: Vec<(usize, Vec<&str>)> = effect + .iter() + .enumerate() + .filter_map(|(i, it)| match it { + Item::Sub("Stylize", items) => Some((i, items.iter().filter_map(|c| if let Item::Cmd(l, ..) = c { Some(*l) } else { None }).collect())), + _ => None, + }) + .collect(); + assert_eq!(stylize.len(), 2); + assert!(stylize[0].0 < raster && raster < stylize[1].0); + assert_eq!(stylize[0].1, ["Round Corners…", "Scribble…", "Drop Shadow…", "Inner Glow…", "Outer Glow…", "Feather…"]); + assert_eq!(stylize[1].1, ["Glowing Edges…"]); +} + #[test] fn library_submenus_are_disabled_placeholders() { let app = app();