Brushes: Yes in Delete Brush asks again when a stroke started using a brush meanwhile

The question for unused brushes checks again when Yes is chosen. If a stroke uses one of the brushes by then, the dialog asks with Remove Strokes and Cancel instead of deleting.
This commit is contained in:
dotCooCoo
2026-11-01 01:21:54 +07:00
committed by bflatastic
parent 3937473e54
commit 3a6770e784
3 changed files with 41 additions and 4 deletions
+21 -1
View File
@@ -28,6 +28,21 @@ pub fn ask(app: &mut VectorcraftApp, params: Value, used: bool) {
app.ui.dialog = Some(Dialog::new(KIND, json!({"params": params, "used": used, "message": message})));
}
/// Whether a stroke uses one of `names` (`brush.unused`), or `None` when the query fails.
pub fn used(app: &mut VectorcraftApp, names: &[String]) -> Option<bool> {
let unused = app.run("brush.unused", json!({})).ok()?;
let unused = unused.get("names").and_then(Value::as_array).cloned().unwrap_or_default();
Some(names.iter().any(|name| !unused.iter().any(|n| n.as_str() == Some(name))))
}
/// The brush names in `brush.delete` params (`name` or `names`).
fn names(params: &Value) -> Vec<String> {
match params.get("name").and_then(Value::as_str) {
Some(name) => vec![name.to_string()],
None => params.get("names").and_then(Value::as_array).into_iter().flatten().filter_map(Value::as_str).map(str::to_string).collect(),
}
}
fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> DialogAction {
let mut action = DialogAction::None;
ui.horizontal(|ui| {
@@ -50,9 +65,14 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> DialogActi
action
}
/// Closes before running, so a dialog the command opens stays open.
/// Closes before running, so a dialog the command opens stays open. Yes on brushes that a stroke
/// has started to use since the question opened asks again with Remove Strokes and Cancel.
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
let params = d.fields.get("params").cloned().unwrap_or_else(|| json!({}));
if !d.bool("used") && used(app, &names(&params)) == Some(true) {
ask(app, params, true);
return Ok(json!({}));
}
app.ui.dialog = None;
app.run("brush.delete", params)
}
@@ -50,6 +50,25 @@ fn visible_yes_and_no_buttons_control_unused_deletion() {
assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1);
}
#[test]
fn yes_asks_again_when_a_stroke_started_using_the_brush() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width": 200, "height": 100})).unwrap();
let name = "10 pt. Oval";
let ctx = context();
ask(&mut app, json!({"names": [name]}), false);
let path = NodeId(app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 60, "height": 40})).unwrap()["id"].as_u64().unwrap());
app.run("brush.apply", json!({"name": name})).unwrap();
let before = app.session.active().unwrap().history.undo.len();
press(&ctx, &mut app, "Yes", 0.0);
assert!(app.ui.dialog.as_ref().is_some_and(|d| d.kind == KIND && d.bool("used")), "asks again with Remove Strokes");
assert_eq!(app.session.active().unwrap().history.undo.len(), before);
let stroke = app.session.active().unwrap().doc.node(path).and_then(|n| n.appearance.stroke()).unwrap();
assert_eq!(stroke.brush.as_deref(), Some(name));
press(&ctx, &mut app, "Remove Strokes", 1.0);
assert!(app.run("brush.get", json!({"name": name})).is_err());
}
#[test]
fn visible_remove_strokes_and_cancel_buttons_control_used_deletion() {
for choice in ["Cancel", "Remove Strokes"] {
+1 -3
View File
@@ -207,11 +207,9 @@ fn run_on(app: &mut VectorcraftApp, id: &str, names: &[String]) {
/// Delete `names` after asking: Yes or No when none of them is in use, else Remove Strokes or
/// Cancel. With `now` (Alt-click on the bottom button), brushes that are not in use go at once.
fn delete(app: &mut VectorcraftApp, names: &[String], now: bool) {
let Ok(unused) = app.run("brush.unused", json!({})) else {
let Some(used) = crate::dialogs::brush_delete::used(app, names) else {
return;
};
let unused = unused.get("names").and_then(Value::as_array).cloned().unwrap_or_default();
let used = names.iter().any(|name| !unused.iter().any(|n| n.as_str() == Some(name)));
if now && !used {
run_on(app, "brush.delete", names);
return;