Delete for everything: selection.delete removes sketch entities, features and sketches (with their dependents), construction planes, bodies (Remove feature), occurrences and components, and canvases in one undo step, with a dry run; Delete/Backspace and every menu's Delete go through it and ask first, listing what goes with it and what would fail; faces say Delete Face is not available yet; ui.confirm control method
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
7f9a8f2afd
commit
a7f301bde6
10 files changed
+476
-33
No files matched your search
@@ -0,0 +1,136 @@
|
||||
//! Delete (the Delete and Backspace keys, and every menu's Delete item): whatever is selected in
|
||||
//! the viewport or the browser goes in one undoable step through `selection.delete`. When it
|
||||
//! takes other features with it or makes some fail, a confirmation lists them first.
|
||||
|
||||
use egui::{RichText, vec2};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::SolveApp;
|
||||
use crate::theme::Tokens;
|
||||
|
||||
/// A delete waiting for confirmation: its parameters and the dry run's report.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Pending {
|
||||
pub params: Value,
|
||||
pub report: Value,
|
||||
}
|
||||
|
||||
/// What Delete acts on now: the selection (bodies not locked), plus the components picked in
|
||||
/// the browser (their occurrences).
|
||||
pub fn selection_params(app: &SolveApp) -> Value {
|
||||
let items: Vec<&solvecraft_engine::Sel> =
|
||||
app.session.selection.iter().filter(|s| !matches!(s, solvecraft_engine::Sel::Body { name } if app.ui.locked_bodies.contains(name))).collect();
|
||||
let occurrences: Vec<u64> = app.tree.picked_components.iter().filter_map(|c| app.session.doc.occurrence_of(*c).map(|o| o.id)).collect();
|
||||
json!({ "items": items, "occurrences": occurrences, "canvases": app.tree.picked_canvases })
|
||||
}
|
||||
|
||||
fn is_empty(p: &Value) -> bool {
|
||||
["items", "occurrences", "components", "canvases"].iter().all(|k| p.get(*k).and_then(Value::as_array).is_none_or(|a| a.is_empty()))
|
||||
}
|
||||
|
||||
/// Delete what is selected (the Delete key). A selected sketch dimension or constraint glyph
|
||||
/// goes first, as before.
|
||||
pub fn delete_selection(app: &mut SolveApp) {
|
||||
if crate::dim_view::delete_selected(app) || crate::sketch_tools::delete_glyph(app) {
|
||||
return;
|
||||
}
|
||||
request(app, selection_params(app));
|
||||
}
|
||||
|
||||
/// Delete `params` (as `selection.delete` takes them): at once, or after a confirmation when
|
||||
/// other features go with it or would fail.
|
||||
pub fn request(app: &mut SolveApp, params: Value) {
|
||||
if is_empty(¶ms) {
|
||||
return;
|
||||
}
|
||||
let mut dry = params.clone();
|
||||
dry["dry_run"] = json!(true);
|
||||
let report = match app.session.execute("selection.delete", &dry) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
app.set_status(e.to_string().replace("selection.delete: ", ""), true);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let asked =
|
||||
params.get("items").and_then(Value::as_array).map_or(0, |a| a.iter().filter(|i| i["type"] == "feature" || i["type"] == "plane").count());
|
||||
let extra = report["deleted"].as_array().map_or(0, Vec::len) > asked;
|
||||
let fails = report["would_fail"].as_array().is_some_and(|a| !a.is_empty());
|
||||
if extra || fails {
|
||||
app.menu.confirm = Some(Pending { params, report });
|
||||
} else {
|
||||
run(app, ¶ms);
|
||||
}
|
||||
}
|
||||
|
||||
fn run(app: &mut SolveApp, params: &Value) {
|
||||
match app.run("selection.delete", params.clone()) {
|
||||
Ok(r) => {
|
||||
app.tree.picked_components.clear();
|
||||
app.tree.picked_canvases.clear();
|
||||
if let Some(s) = r["skipped"].as_array().and_then(|a| a.first()).and_then(Value::as_str) {
|
||||
app.set_status(format!("deleted; {s}"), false);
|
||||
}
|
||||
}
|
||||
Err(e) => app.set_status(e.replace("selection.delete: ", ""), true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Answer the confirmation (also `ui.confirm`): delete, or keep everything.
|
||||
pub fn confirm(app: &mut SolveApp, accept: bool) -> bool {
|
||||
let Some(p) = app.menu.confirm.take() else { return false };
|
||||
if accept {
|
||||
run(app, &p.params);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// The confirmation window.
|
||||
pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let Some(p) = app.menu.confirm.clone() else { return };
|
||||
let t = Tokens::get();
|
||||
let list = |k: &str| -> Vec<String> {
|
||||
p.report[k].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_default()
|
||||
};
|
||||
let (deleted, fails) = (list("deleted"), list("would_fail"));
|
||||
let mut answer: Option<bool> = None;
|
||||
egui::Window::new("Delete")
|
||||
.id(egui::Id::new("sc_delete_confirm"))
|
||||
.collapsible(false)
|
||||
.resizable(false)
|
||||
.anchor(egui::Align2::CENTER_CENTER, vec2(0.0, -60.0))
|
||||
.show(ctx, |ui| {
|
||||
ui.set_max_width(360.0);
|
||||
if !deleted.is_empty() {
|
||||
ui.label("These features will be deleted:");
|
||||
for d in deleted.iter().take(20) {
|
||||
ui.label(RichText::new(format!(" • {d}")).color(t.text));
|
||||
}
|
||||
if deleted.len() > 20 {
|
||||
ui.label(format!(" … and {} more", deleted.len() - 20));
|
||||
}
|
||||
}
|
||||
if !fails.is_empty() {
|
||||
ui.add_space(6.0);
|
||||
ui.label(RichText::new("These features will fail:").color(t.warning));
|
||||
for f in fails.iter().take(20) {
|
||||
ui.label(RichText::new(format!(" • {f}")).color(t.warning));
|
||||
}
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Delete").clicked() {
|
||||
answer = Some(true);
|
||||
}
|
||||
if ui.button("Cancel").clicked() {
|
||||
answer = Some(false);
|
||||
}
|
||||
});
|
||||
if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
answer = Some(false);
|
||||
}
|
||||
});
|
||||
if let Some(a) = answer {
|
||||
confirm(app, a);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user