Delete key removes selected content object in edit mode (#6384)

* Delete key removes selected content object in edit mode

The reporter says Delete does nothing for a selected textbox or image in edit mode; the only Delete/Backspace handlers in the UI are for organize pages and comments, so a selected content object has no delete path.

Add Delete/Backspace handling to the object selection input in object_ui.rs that deletes the currently selected object(s), and cover it with a unit test that selects an object and presses Delete.

Test: delete_key_removes_selected_object failed before the change and passes after.

* Tests: the Delete key test takes PageToolContext and imports PagePointer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: eliets2 <287273517+eliets2@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
eliets2
2026-11-06 10:10:16 +01:00
committed by GitHub
co-authored by Claude Opus 5.5 eliets2 FredApps
parent 97cef90e01
commit af76b5ca89
+72
View File
@@ -218,6 +218,22 @@ pub(crate) fn page_input(cx: PageToolContext<'_>, view: &mut DocView) -> bool {
}
return false;
}
// Delete or Backspace removes the selected objects on this page as one edit,
// like Edit::DeletePages removes pages. The key is consumed even when the pointer
// is off the page, because canvas keys belong to the selection while it exists.
if view.objects.page == Some(page)
&& !view.objects.selected.is_empty()
&& view.objects.gesture.is_none()
&& view.pending_edit.is_none()
&& !ui.ctx().egui_wants_keyboard_input()
{
let deleted =
ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Delete) || i.consume_key(egui::Modifiers::NONE, egui::Key::Backspace));
if deleted {
view.pending_edit = delete_edit(page, view.objects.selected.iter().map(|o| o.target).collect());
return true;
}
}
let mut selection = std::mem::take(&mut view.objects);
let result = input(&mut selection, cx, view);
view.objects = selection;
@@ -527,4 +543,60 @@ mod tests {
assert!(matches!(delete_edit(2, vec![t(ObjectKind::Image, 4)]), Some(Edit::EditPageImage { page: 2, index: 4, .. })));
assert!(delete_edit(0, Vec::new()).is_none());
}
/// Selecting content objects on a page and pressing Delete must queue one edit that removes
/// them: `page_input` selects and moves content objects but never consumes Delete or
/// Backspace, so pressing Delete currently does nothing (issue #3780).
#[test]
fn delete_key_removes_selected_object() {
use crate::canvas::page_pointer::PagePointer;
use crate::{PdfCraftApp, canvas::ViewDefaults};
use pdfcraft_engine::{DocId, ImageEdit};
use pdfcraft_render::PageInfo;
let mut app = PdfCraftApp::new();
let bytes = app.session.create_blank(300.0, 400.0, 1).unwrap();
app.open_bytes("blank.pdf", None, bytes.as_ref().clone()).unwrap();
let doc = app.session.get(app.views[0].id).unwrap();
let info = pdfcraft_render::DocInfo {
pages: vec![PageInfo { width: 300.0, height: 400.0, label: String::new(), crop: [0.0, 0.0, 300.0, 400.0], rotation: 0 }],
..Default::default()
};
let mut view = DocView::new(DocId(1), &info, ViewDefaults::default());
view.objects.page = Some(0);
view.objects.selected.push(EditableObject {
target: ObjectTarget { kind: ObjectKind::Image, index: 0 },
rect: [50.0, 60.0, 100.0, 110.0],
content_type: "image",
text: None,
});
let ctx = egui::Context::default();
let xf = PageXform { rect: Rect::from_min_size(Pos2::ZERO, egui::vec2(800.0, 600.0)), rot: 0, pw: 800.0, ph: 600.0 };
let ptr = PagePointer { pos: None, ..Default::default() };
let mut output = ctx.run_ui(
egui::RawInput {
screen_rect: Some(Rect::from_min_size(Pos2::ZERO, egui::vec2(800.0, 600.0))),
events: vec![egui::Event::Key {
key: egui::Key::Delete,
physical_key: None,
pressed: true,
repeat: false,
modifiers: egui::Modifiers::NONE,
}],
..Default::default()
},
|ui| {
let resp = ui.interact(ui.available_rect_before_wrap(), egui::Id::unique("page"), egui::Sense::click());
page_input(PageToolContext { ui, resp: &resp, xf: &xf, ptr: &ptr, page: 0, doc, info: &info }, &mut view);
},
);
output.textures_delta.clear();
assert!(
matches!(&view.pending_edit, Some(Edit::EditPageImage { page: 0, index: 0, change: ImageEdit::Delete })),
"pressing Delete with one object selected must queue that object's deletion as one edit"
);
}
}