mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 14:38:13 -06:00
fix(ui): Redo after Undo in a page text box puts the typing back
With typing undone in a paragraph or added text box, its text matches the page again, so Undo and Redo went to the document. Redo then closed the box and redid a document change (or nothing), instead of putting the typing back as the box's own Redo did before. And Shift+Cmd+Z is also Cmd+Z to the Undo shortcut, so even a Redo the box kept was taken as a document Undo. Redo stays with the box while its own history has typing to put back (the test egui makes when it handles the key), and Shift+Cmd+Z is never a document Undo.
This commit is contained in:
@@ -727,8 +727,14 @@ impl PdfCraftApp {
|
||||
continue;
|
||||
}
|
||||
// A box typed into on the page (a paragraph being edited, added text) keeps Undo and
|
||||
// Redo only while it holds typing the page doesn't have; then they reach the document.
|
||||
let page_box = matches!(spec.id, "edit.undo" | "edit.redo") && self.page_box_focused(ctx) && !self.page_typing_unapplied();
|
||||
// Redo only while it holds typing the page doesn't have, or Redo has typing to put
|
||||
// back; then they reach the document. ⌘⇧Z is also ⌘Z to the Undo shortcut, so a
|
||||
// Redo the box keeps is never taken as Undo.
|
||||
let page_box = matches!(spec.id, "edit.undo" | "edit.redo")
|
||||
&& self.page_box_focused(ctx)
|
||||
&& !self.page_typing_unapplied()
|
||||
&& !(spec.id == "edit.redo" && self.page_box_can_redo(ctx))
|
||||
&& !(spec.id == "edit.undo" && ctx.input(|i| i.modifiers.shift));
|
||||
if typing && !spec.in_text && !page_box {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -293,6 +293,27 @@ impl PdfCraftApp {
|
||||
focused == Some(crate::edit_text_ui::input_id()) || focused == Some(crate::content_ui::input_id())
|
||||
}
|
||||
|
||||
/// Whether the focused box on the page has typing its own Redo would put back: after Undo in
|
||||
/// the box restores what the page has, Redo still belongs to the box, not the document. The
|
||||
/// same test egui makes when it handles the key.
|
||||
pub(crate) fn page_box_can_redo(&self, ctx: &egui::Context) -> bool {
|
||||
let Some(view) = self.active.and_then(|i| self.views.get(i)) else { return false };
|
||||
let focused = ctx.memory(|m| m.focused());
|
||||
let (id, text) = if focused == Some(crate::edit_text_ui::input_id())
|
||||
&& let Some(ed) = &view.line_editor
|
||||
{
|
||||
(crate::edit_text_ui::input_id(), &ed.text)
|
||||
} else if focused == Some(crate::content_ui::input_id())
|
||||
&& let Some(d) = &view.content.draft
|
||||
{
|
||||
(crate::content_ui::input_id(), &d.text)
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
egui::TextEdit::load_state(ctx, id)
|
||||
.is_some_and(|state| state.cursor.char_range().is_some_and(|range| state.undoer().has_redo(&(range, text.clone()))))
|
||||
}
|
||||
|
||||
/// Typing in a box on the page (a paragraph being edited, added text) that the page doesn't
|
||||
/// have yet: Undo and Redo keys take it back in the box first.
|
||||
pub(crate) fn page_typing_unapplied(&self) -> bool {
|
||||
|
||||
@@ -915,3 +915,33 @@ fn aligning_a_selected_line_of_added_text_aligns_only_that_line() {
|
||||
assert_eq!(aligns(&h), (pdfcraft_engine::TextAlign::Left, vec![]), "Undo puts it back");
|
||||
assert!(h.state().views[0].content.draft.is_none(), "and closes the box");
|
||||
}
|
||||
|
||||
/// The same in an added text box: Redo after Undo puts the typing back in the box, and the
|
||||
/// document's own Redo stays waiting.
|
||||
#[test]
|
||||
fn redo_after_undoing_typing_in_added_text_puts_it_back() {
|
||||
let mut h = harness();
|
||||
let rect = [40.0, 195.0, 240.0, 210.0];
|
||||
let text = pdfcraft_engine::AddedText { rect, text: "Reviewed".into(), ..Default::default() };
|
||||
assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::AddText { page: 0, text: text.clone() }));
|
||||
let bold = pdfcraft_engine::AddedText { bold: true, ..text };
|
||||
assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::UpdateContent { page: 0, index: 0, content: pdfcraft_engine::AddedContent::Text(bold) }));
|
||||
assert!(h.state_mut().execute("edit.undo"));
|
||||
assert!(h.state_mut().execute("edit.text"));
|
||||
h.run_steps(3);
|
||||
let point = at(&h, 45.0, 195.0);
|
||||
click(&mut h, point);
|
||||
assert_eq!(h.state().views[0].content.draft.as_ref().and_then(|d| d.index), Some(0), "the editor opened on the text");
|
||||
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::End);
|
||||
h.event(egui::Event::Text(" twice".into()));
|
||||
h.run_steps(2);
|
||||
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::Z);
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views[0].content.draft.as_ref().map(|d| d.text.as_str()), Some("Reviewed"));
|
||||
h.key_press_modifiers(egui::Modifiers::COMMAND | egui::Modifiers::SHIFT, egui::Key::Z);
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views[0].content.draft.as_ref().map(|d| d.text.as_str()), Some("Reviewed twice"), "Redo puts the typing back");
|
||||
let s = h.state();
|
||||
let doc = s.session.get(s.views[0].id).unwrap();
|
||||
assert_eq!(doc.can_redo(), Some("Edit content"), "the document's Redo still waits");
|
||||
}
|
||||
|
||||
@@ -6127,3 +6127,34 @@ fn aligning_a_selected_row_aligns_only_that_row_and_undo_puts_it_back() {
|
||||
assert!(s.views[0].line_editor.is_none(), "the editor closed with the step it showed");
|
||||
assert!((row_rect(&h, "nine")[0] - last[0]).abs() < 0.5, "the last row is back");
|
||||
}
|
||||
|
||||
/// Redo after Undo in the box puts the typing back, in the box: it doesn't reach the document,
|
||||
/// whose own Redo (a formatting change waiting here) must stay waiting.
|
||||
#[test]
|
||||
fn redo_after_undoing_typing_puts_the_typing_back() {
|
||||
let mut h = harness(1, |_| {});
|
||||
assert!(h.state_mut().execute("edit.edit_text"));
|
||||
h.run_steps(2);
|
||||
open_paragraph(&mut h);
|
||||
h.get_by_label("Bold").click();
|
||||
h.run_steps(4);
|
||||
// Undo the bold (closing the editor), so the document has it to redo.
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::Z);
|
||||
h.run_steps(4);
|
||||
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().can_redo(), Some("Edit text"));
|
||||
open_paragraph(&mut h);
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::End);
|
||||
h.event(egui::Event::Text(" more".into()));
|
||||
h.run_steps(2);
|
||||
let typed = h.state().views[0].line_editor.as_ref().unwrap().text.clone();
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::Z);
|
||||
h.run_steps(3);
|
||||
assert_ne!(h.state().views[0].line_editor.as_ref().unwrap().text, typed, "the typing is taken back");
|
||||
h.key_press_modifiers(Modifiers::COMMAND | Modifiers::SHIFT, Key::Z);
|
||||
h.run_steps(3);
|
||||
let s = h.state();
|
||||
let ed = s.views[0].line_editor.as_ref().expect("still typing");
|
||||
assert_eq!(ed.text, typed, "Redo puts the typing back");
|
||||
let doc = s.session.get(s.views[0].id).unwrap();
|
||||
assert_eq!((doc.can_redo(), doc.text_blocks(0)[0].base_font.as_str()), (Some("Edit text"), "Helvetica"), "the document's Redo still waits");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user