mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-02 14:38:13 -06:00
fix(ui): a text box keeps its typing history through a trip to another tab
The tabs share one text box, and its Undo history was cleared whenever another document's box took it: opening a paragraph in tab B wiped tab A's typing steps, so Edit ▸ Undo back in A found none and closed A's paragraph instead of taking the typing back. The box being left now keeps a copy of its history and caret aside, under its document, and the same box gets it back on its return; a box new to the text box still starts with none, so typing still never reaches another tab. The copy is a deep one: a state's history is shared between its clones.
This commit is contained in:
@@ -276,15 +276,33 @@ pub(crate) fn input_id() -> egui::Id {
|
||||
/// as it was before a format change, into a box showing the page as it is now. `from` is the box's
|
||||
/// own record of that; which document `doc` last used the history is kept beside it, since a box
|
||||
/// in another tab on the same text would otherwise take that document's typing with its Redo.
|
||||
/// When another document's box takes the text box, the box it leaves keeps its history (and
|
||||
/// caret) aside, and gets it back on its return, so its typing can still be undone there.
|
||||
pub(crate) fn forget_typing_before(ctx: &egui::Context, id: egui::Id, doc: pdfcraft_engine::DocId, from: &mut Option<String>, text: &str) {
|
||||
let owner = id.with("typing-doc");
|
||||
if from.as_deref() == Some(text) && ctx.data(|d| d.get_temp::<pdfcraft_engine::DocId>(owner)) == Some(doc) {
|
||||
let last = ctx.data(|d| d.get_temp::<pdfcraft_engine::DocId>(owner));
|
||||
if from.as_deref() == Some(text) && last == Some(doc) {
|
||||
return;
|
||||
}
|
||||
if let Some(mut state) = egui::TextEdit::load_state(ctx, id) {
|
||||
state.clear_undoer();
|
||||
egui::TextEdit::store_state(ctx, id, state);
|
||||
let aside = |doc: pdfcraft_engine::DocId| id.with(("typing-aside", doc));
|
||||
if let Some(left) = last.filter(|l| *l != doc)
|
||||
&& let Some(state) = egui::TextEdit::load_state(ctx, id)
|
||||
{
|
||||
// A copy of its own: a state's history is shared between its clones, and the next box
|
||||
// clears it in place.
|
||||
let mut kept = egui::text_edit::TextEditState::default();
|
||||
kept.cursor = state.cursor;
|
||||
kept.set_undoer(state.undoer());
|
||||
ctx.data_mut(|d| d.insert_temp(aside(left), kept));
|
||||
}
|
||||
// Only the same box, still on the text it opened on, takes its history back.
|
||||
let back = ctx.data_mut(|d| d.remove_temp::<egui::text_edit::TextEditState>(aside(doc))).filter(|_| from.as_deref() == Some(text));
|
||||
let state = back.unwrap_or_else(|| {
|
||||
let mut state = egui::TextEdit::load_state(ctx, id).unwrap_or_default();
|
||||
state.clear_undoer();
|
||||
state
|
||||
});
|
||||
egui::TextEdit::store_state(ctx, id, state);
|
||||
ctx.data_mut(|d| d.insert_temp(owner, doc));
|
||||
*from = Some(text.to_owned());
|
||||
}
|
||||
|
||||
@@ -1022,6 +1022,42 @@ fn redo_from_the_menu_is_enabled_for_the_added_text_boxs_own_typing() {
|
||||
assert_eq!(h.state().views[0].content.draft.as_ref().map(|d| d.text.as_str()), Some("Reviewed twice"), "Redo puts it back");
|
||||
}
|
||||
|
||||
/// Typing in tab A's added text box, the same text opened in tab B, then back to A: Edit ▸ Undo
|
||||
/// takes the typing back in A's box, which stays open.
|
||||
#[test]
|
||||
fn undo_after_a_round_trip_through_another_tab_takes_back_added_text_typing() {
|
||||
let mut h = harness();
|
||||
h.state_mut().open_bytes("copy.pdf", None, include_bytes!("../data/form.pdf").to_vec()).unwrap();
|
||||
h.run_steps(4);
|
||||
let text = pdfcraft_engine::AddedText { rect: [40.0, 195.0, 240.0, 210.0], text: "Reviewed".into(), ..Default::default() };
|
||||
let open_in = |h: &mut Harness<'static, PdfCraftApp>, tab: usize| {
|
||||
h.state_mut().active = Some(tab);
|
||||
h.run_steps(2);
|
||||
assert!(h.state_mut().execute("edit.text"));
|
||||
h.run_steps(3);
|
||||
let r = h.state().views[tab].page_screen_rect(0).expect("on screen");
|
||||
let k = r.width() / 300.0;
|
||||
click(h, r.min + egui::vec2(45.0 * k, 195.0 * k));
|
||||
assert_eq!(h.state().views[tab].content.draft.as_ref().and_then(|d| d.index), Some(0), "the editor opened on the text");
|
||||
};
|
||||
for tab in [0, 1] {
|
||||
h.state_mut().active = Some(tab);
|
||||
assert!(h.state_mut().apply_edit(pdfcraft_engine::Edit::AddText { page: 0, text: text.clone() }));
|
||||
}
|
||||
open_in(&mut h, 0);
|
||||
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::End);
|
||||
h.event(egui::Event::Text(" twice".into()));
|
||||
h.run_steps(2);
|
||||
open_in(&mut h, 1);
|
||||
h.state_mut().active = Some(0);
|
||||
h.run_steps(3);
|
||||
let draft = |h: &Harness<'static, PdfCraftApp>| h.state().views[0].content.draft.as_ref().map(|d| d.text.clone());
|
||||
assert_eq!(draft(&h).as_deref(), Some("Reviewed twice"), "A's box is still open with the typing");
|
||||
assert!(h.state_mut().execute("edit.undo"));
|
||||
h.run_steps(3);
|
||||
assert_eq!(draft(&h).as_deref(), Some("Reviewed"), "Undo takes the typing back in A's box");
|
||||
}
|
||||
|
||||
/// Redo with an added text box open and unchanged reaches the document's waiting Redo, even when
|
||||
/// the box's text ends in a space: closing the unchanged box writes nothing, so it can't clear
|
||||
/// that Redo first.
|
||||
|
||||
@@ -6645,6 +6645,49 @@ fn redo_from_the_menu_is_enabled_for_the_boxs_own_typing() {
|
||||
assert_eq!(h.state().views[0].line_editor.as_ref().expect("still open").text, typed, "Redo puts the typing back");
|
||||
}
|
||||
|
||||
/// Typing in tab A's paragraph, a paragraph opened in tab B, then back to A: Edit ▸ Undo takes
|
||||
/// the typing back in A's box. The tabs share one text box, and its history was cleared each time
|
||||
/// another tab's box took it, so Undo found no step and closed A's paragraph instead
|
||||
/// (skylartaylor's and FredApps's review of 08ec1db7).
|
||||
#[test]
|
||||
fn undo_after_a_round_trip_through_another_tab_takes_back_the_typing() {
|
||||
let mut h = harness(1, |app| {
|
||||
app.open_bytes("copy.pdf", None, fixture(1)).expect("the copy opens");
|
||||
});
|
||||
let open_in = |h: &mut Harness<'static, PdfCraftApp>, tab: usize| {
|
||||
h.state_mut().active = Some(tab);
|
||||
h.run_steps(2);
|
||||
if h.state().views[tab].line_editor.is_none() {
|
||||
assert!(h.state_mut().execute("edit.edit_text"));
|
||||
h.run_steps(2);
|
||||
}
|
||||
let r = h.state().views[tab].page_screen_rect(0).expect("on screen");
|
||||
click_at(h, egui::pos2(r.left() + 40.0 / 200.0 * r.width(), r.top() + (300.0 - 158.0) / 300.0 * r.height()));
|
||||
assert!(h.state().views[tab].line_editor.is_some(), "the paragraph opens for typing");
|
||||
};
|
||||
open_in(&mut h, 0);
|
||||
h.get_by_label("Bold").click();
|
||||
h.run_steps(4);
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::Z);
|
||||
h.run_steps(4);
|
||||
open_in(&mut h, 0);
|
||||
let original = h.state().views[0].line_editor.as_ref().unwrap().text.clone();
|
||||
h.key_press_modifiers(Modifiers::COMMAND, Key::End);
|
||||
h.event(egui::Event::Text(" more".into()));
|
||||
h.run_steps(2);
|
||||
assert!(h.state().views[0].line_editor.as_ref().unwrap().text.ends_with(" more"));
|
||||
open_in(&mut h, 1);
|
||||
h.state_mut().active = Some(0);
|
||||
h.run_steps(3);
|
||||
assert!(h.state().views[0].line_editor.is_some(), "A's paragraph is still open after the round trip");
|
||||
assert!(h.state_mut().execute("edit.undo"), "Undo is enabled");
|
||||
h.run_steps(3);
|
||||
let s = h.state();
|
||||
let ed = s.views[0].line_editor.as_ref().expect("the box stays open: Undo takes back the typing");
|
||||
assert_eq!(ed.text, original, "the typing is taken back in the box");
|
||||
assert_eq!(s.session.get(s.views[0].id).unwrap().can_redo(), Some("Edit text"), "the document's Redo still waits");
|
||||
}
|
||||
|
||||
/// Two copies of a document, the same paragraph open in each. The paragraph boxes share one text
|
||||
/// box, and so one Undo history: a box opened on the same text as another tab's didn't clear it.
|
||||
/// Typing in one tab, taken back with Undo, then came back with Redo in the other tab.
|
||||
|
||||
Reference in New Issue
Block a user