From 23a0772776db29efac79883dd71d12cff05e6e50 Mon Sep 17 00:00:00 2001 From: Khawar Zamman Wani Date: Thu, 12 Nov 2026 15:39:41 +0530 Subject: [PATCH] Custom Shape: refuse an operation for a shape that can't be one component Its parts would all take the operation and change the shape. --- .../vector_ui/ellipse_path_menus_tests.rs | 24 +++++++++++++++++++ crates/ui-egui/src/tools/vector_ui/shapes.rs | 12 +++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/crates/ui-egui/src/tools/vector_ui/ellipse_path_menus_tests.rs b/crates/ui-egui/src/tools/vector_ui/ellipse_path_menus_tests.rs index 472d902d80..11b3d5c49a 100644 --- a/crates/ui-egui/src/tools/vector_ui/ellipse_path_menus_tests.rs +++ b/crates/ui-egui/src/tools/vector_ui/ellipse_path_menus_tests.rs @@ -172,3 +172,27 @@ fn a_custom_shape_with_an_operation_joins_the_active_shape_layer() { let ops: Vec = active_shape(&app).path.subpaths.iter().map(|s| s.op).collect(); assert_eq!(ops, [PathOp::Combine, PathOp::Subtract, PathOp::Join], "the ellipse, the ring and its hole"); } + +/// #13442 review: a custom shape that can't be made one component (its parts exclude each other) +/// is not added with an operation, which would change its parts' operations: the layer stays as +/// it was and the status says why. +#[test] +fn a_custom_shape_that_is_not_one_component_is_refused() { + use photocraft_doc::vector::{Path, Subpath}; + let mut app = app(); + drag(&mut app, [50.0, 50.0], [200.0, 200.0]); + let before = active_shape(&app).path; + let xor = Path::new(vec![ + Subpath::polygon(&[(0.0, 0.0), (60.0, 0.0), (60.0, 60.0), (0.0, 60.0)]), + Subpath::polygon(&[(40.0, 40.0), (100.0, 40.0), (100.0, 100.0), (40.0, 100.0)]).with_op(PathOp::Exclude), + ]); + let r = app.run("shape.presets.new", json!({"name": "Xor", "path": photocraft_engine::vector_cmds::path_json(&xor)})).unwrap(); + app.ui.tool = Tool::CustomShape; + app.ui.presets_ui.custom_shape = r["name"].as_str().unwrap().into(); + app.ui.tool_options.shape_path_op = "combine".into(); + let layers = layer_count(&app); + drag(&mut app, [100.0, 100.0], [250.0, 250.0]); + assert_eq!(layer_count(&app), layers); + assert_eq!(active_shape(&app).path, before, "the layer is unchanged"); + assert!(app.ui.status_error, "{}", app.ui.status); +} diff --git a/crates/ui-egui/src/tools/vector_ui/shapes.rs b/crates/ui-egui/src/tools/vector_ui/shapes.rs index df08567d54..4bf280dce0 100644 --- a/crates/ui-egui/src/tools/vector_ui/shapes.rs +++ b/crates/ui-egui/src/tools/vector_ui/shapes.rs @@ -179,7 +179,17 @@ pub fn finish_shape(app: &mut PhotocraftApp, tool: Tool, start: [f64; 2], end: [ && let Some(id) = super::options::selected_shape(&app.session) && let Some(outline) = shape_outline(app, tool, start, end, mods) { - let outline = as_one_component(&outline, outline.fill_rule).unwrap_or(outline); + // `shape.create` gives every component the operation, so a shape of several components + // that can't be made one (its parts intersect or exclude) would change: refuse it. + let outline = match as_one_component(&outline, outline.fill_rule) { + Some(one) => one, + None if outline.subpaths.len() <= 1 => outline, + None => { + app.ui.status = tl!("This custom shape can't be added to a shape layer with a path operation: choose New Layer").into(); + app.ui.status_error = true; + return; + } + }; let path = photocraft_engine::vector_cmds::path_json(&outline); if let Err(e) = app.run("shape.create", json!({"kind": "path", "path": path, "addTo": id.0, "op": op})) { app.ui.status = e;