Merge pull request #116 from storytold/fix/93-95-prepare-form

Prepare a form: Detect form fields in the panel, delete several fields at once (#93, #95)
This commit is contained in:
BFlat 2026-10-07 09:46:48 +07:00 committed by GitHub
commit 3751978d0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 10 deletions

View File

@ -323,6 +323,7 @@ pub static TOOL_GROUPS: &[ToolGroup] = &[
title: "Fields",
items: &[
item("Field list", "list", "form.fields", Ready),
item("Detect form fields", "scan", "form.detect", Ready),
item("Clear form", "eraser", "form.clear", Ready),
item("Field properties", "settings-2", "form.field.properties", Ready),
item("Tab order by rows", "rows-3", "form.tab_order.row", Ready),

View File

@ -1485,9 +1485,9 @@ pub fn document_area(app: &mut PrintCraftApp, index: usize, ui: &mut egui::Ui) {
ui.close();
}
ui.separator();
if ui.add_enabled(can_modify, egui::Button::new("Delete")).clicked() {
view.prepare.selected = None;
view.pending_edit = Some(printcraft_engine::Edit::DeleteField { name });
let delete = if view.prepare.also.is_empty() { "Delete" } else { "Delete selected fields" };
if ui.add_enabled(can_modify, egui::Button::new(delete)).clicked() {
view.pending_edit = crate::prepare::delete_selected(view);
ui.close();
}
return;

View File

@ -498,16 +498,34 @@ pub(crate) fn paint_page(ui: &egui::Ui, painter: &egui::Painter, xf: &PageXform,
}
}
/// Delete removes the selected field; Escape clears the selection.
/// Delete the selected field and every field selected with it (#95), as one undoable step, and
/// clear the selection.
pub(crate) fn delete_selected(view: &mut DocView) -> Option<Edit> {
let (first, _) = view.prepare.selected.take()?;
let mut names = vec![first];
for (name, _) in std::mem::take(&mut view.prepare.also) {
if !names.contains(&name) {
names.push(name);
}
}
if names.len() == 1 {
return names.pop().map(|name| Edit::DeleteField { name });
}
let label = format!("Delete {} fields", names.len());
Some(Edit::Batch { label, edits: names.into_iter().map(|name| Edit::DeleteField { name }).collect() })
}
/// Delete removes the selected fields; Escape clears the selection.
pub(crate) fn keys(ctx: &egui::Context, view: &mut DocView) {
if view.prepare.selected.is_none() || ctx.egui_wants_keyboard_input() {
return;
}
let (del, esc) = ctx.input(|i| (i.key_pressed(egui::Key::Delete) || i.key_pressed(egui::Key::Backspace), i.key_pressed(egui::Key::Escape)));
if del && let Some((name, _)) = view.prepare.selected.take() {
view.pending_edit = Some(Edit::DeleteField { name });
if del {
view.pending_edit = delete_selected(view);
} else if esc {
view.prepare.selected = None;
view.prepare.also.clear();
}
}

View File

@ -285,6 +285,35 @@ fn aligning_distributing_and_sizing_several_fields() {
assert!(arrange(&form(&h), &a, &[("b".into(), 0)], Arrange::DistributeV).is_none(), "distributing needs three");
}
#[test]
fn deleting_several_selected_fields_and_detecting_fields_from_the_panel() {
let mut h = harness();
for (name, x) in [("a", 20.0), ("b", 120.0), ("c", 220.0)] {
h.state_mut().apply_edit(printcraft_engine::Edit::AddField {
page: 0,
rect: [x, 300.0, x + 60.0, 320.0],
kind: printcraft_engine::NewField::Text { multiline: false },
name: Some(name.into()),
});
}
assert!(h.state_mut().execute("form.prepare"));
h.run_steps(2);
// #93: field detection is in the panel, not only run when a form has no fields.
h.get_by_label("Detect form fields");
// #95: Delete removes every selected field, as one undoable step.
{
let p = &mut h.state_mut().views[0].prepare;
p.selected = Some(("a".into(), 0));
p.also = vec![("b".into(), 0), ("c".into(), 0)];
}
h.key_press(egui::Key::Delete);
h.run_steps(3);
let left = names(&h);
assert!(!["a", "b", "c"].iter().any(|n| left.iter().any(|l| l == n)), "{left:?}");
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().can_undo(), Some("Delete 3 fields"));
assert!(h.state().views[0].prepare.selected.is_none() && h.state().views[0].prepare.also.is_empty());
}
#[test]
fn preview_fills_the_form_and_locked_fields_keep_their_properties() {
let mut h = harness();

View File

@ -4650,8 +4650,8 @@ milestone = "M6"
status = "shipped"
commands = ["form.prepare"]
tools = ["form_add_field", "form_set_props", "form_delete_field"]
evidence = ["crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/engine/src/tests.rs::preparing_a_form_adds_renames_and_deletes_fields"]
notes = "fields shown with blue fill and name tags; select, move, resize by corners, Delete; field tools return to Select after one field"
evidence = ["crates/ui-egui/tests/prepare.rs::placing_moving_editing_and_deleting_a_field", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel", "crates/engine/src/tests.rs::preparing_a_form_adds_renames_and_deletes_fields"]
notes = "fields shown with blue fill and name tags; select, move, resize by corners, Delete (every selected field, one undo step); field tools return to Select after one field. Not yet: editing shared properties of several fields at once"
[[feature]]
id = "form.add-text-field"
@ -4986,8 +4986,8 @@ milestone = "M6"
status = "partial"
commands = ["form.detect", "form.prepare"]
tools = ["form_detect_fields"]
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools", "crates/ui-egui/tests/javascript.rs::prepare_a_form_detects_fields_on_a_paper_form"]
notes = "Underscore runs, rules, empty boxes and small squares (check boxes); runs when Prepare a form starts on a document without fields. Not yet: comb boxes, tables of cells, scanned forms (needs OCR first), radio groups."
evidence = ["crates/forms/src/tests.rs::detection_finds_blanks_rules_boxes_and_names_them", "crates/engine/src/tests.rs::detecting_fields_on_a_printed_form", "crates/automation/tests/automation.rs::detecting_form_fields_through_tools", "crates/ui-egui/tests/javascript.rs::prepare_a_form_detects_fields_on_a_paper_form", "crates/ui-egui/tests/prepare.rs::deleting_several_selected_fields_and_detecting_fields_from_the_panel"]
notes = "Underscore runs, rules, empty boxes and small squares (check boxes); runs when Prepare a form starts on a document without fields, and from Prepare a form ▸ Detect form fields at any time (existing fields are skipped). Not yet: comb boxes, tables of cells, scanned forms (needs OCR first), radio groups."
[[feature]]
id = "form.auto-name-fields"