M5: Escape leaves a Fill & Sign tool, as it leaves a comment tool (#4539)

* Escape leaves a Fill & Sign tool, as it leaves a comment tool

The first Escape closes the open type box, the next one goes back to
Select; Fill & Sign's Text tool otherwise opened a box at every click with
no way out by the keyboard.

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

* Fill & Sign: a new text box deselects, so two presses of Escape close it and leave the tool

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ohagmannandClaude Opus 5.5 authored and GitHub committed 2026-11-05 13:13:56 +01:00
1 parent 863af864a7
commit 8d569066b8
5 files changed
+64 -6

No files matched your search

+4 -2
View File
@@ -606,9 +606,11 @@ pub(crate) fn keys(ctx: &egui::Context, view: &mut DocView, tool: &mut QuickTool
}
let cv = &mut view.comments;
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
// One step back per press: cancel the gesture, then deselect, then drop the tool.
// One step back per press: cancel the gesture, then deselect, then drop the tool (a
// comment tool, or a Fill & Sign one: its Text tool would otherwise open a box at every
// click with no way out by the keyboard).
let cancelled = cv.gesture.take().is_some() || cv.selected.take().is_some();
if !cancelled && matches!(tool, QuickTool::Comment(_)) {
if !cancelled && matches!(tool, QuickTool::Comment(_) | QuickTool::Fill(_)) {
*tool = QuickTool::Select;
}
}
+6 -2
View File
@@ -491,7 +491,10 @@ pub(crate) fn page_input(cx: PageToolContext<'_>, input: PageInput<'_>, free: bo
match tool {
FillTool::Text => {
// Clicking away from an open box commits it: the page sees this click before the box
// sees its lost focus, so replacing the box first would drop what was typed.
// sees its lost focus, so replacing the box first would drop what was typed. A new box
// deselects, as a comment tool's note or text box does: Escape then closes the box and
// leaves the tool in two presses.
view.comments.selected = None;
let open = view.fill_text.replace(TypeBox { page, at: top, text: String::new(), size: text_size, color: text_color, focus: true });
open.filter(|tb| !tb.text.trim().is_empty()).map(|tb| {
FillAction::Edit(Box::new(typed_with_color(
@@ -587,7 +590,8 @@ pub(crate) fn type_box(ctx: &egui::Context, view: &mut DocView, info: &DocInfo,
r.request_focus();
t.focus = false;
}
if ui.input(|i| i.key_pressed(egui::Key::Escape)) {
// Taken here: the same press must not also leave the tool (one step back per press).
if ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Escape)) {
cancel = true;
} else if r.lost_focus() {
commit = true;
+51
View File
@@ -159,6 +159,57 @@ fn clicking_elsewhere_with_add_text_keeps_the_typed_text() {
assert!(h.state().views[0].fill_text.as_ref().is_some_and(|t| t.text.is_empty()), "a new, empty box is open");
}
/// Escape steps back out of a Fill & Sign tool as out of a comment tool: the first press closes
/// the open type box, the next one leaves the Text tool for Select, so a click opens no new box.
#[test]
fn escape_leaves_the_fill_and_sign_tools() {
let mut h = harness();
h.state_mut().execute("sign.fill.text");
click(&mut h, 50.0, 250.0);
assert!(h.state().views[0].fill_text.is_some(), "a box is open");
h.key_press(egui::Key::Escape);
h.run_steps(2);
assert!(h.state().views[0].fill_text.is_none(), "the first Escape closes the box");
assert_eq!(h.state().quick_tool, QuickTool::Fill(FillTool::Text), "the tool stays for the next box");
h.key_press(egui::Key::Escape);
h.run_steps(2);
assert_eq!(h.state().quick_tool, QuickTool::Select, "the next Escape leaves the tool");
click(&mut h, 50.0, 150.0);
assert!(h.state().views[0].fill_text.is_none(), "no new box");
assert!(items(&h).is_empty(), "nothing was added: {:?}", items(&h));
// A mark tool too.
h.state_mut().execute("sign.fill.check");
h.run_steps(2);
assert_eq!(h.state().quick_tool, QuickTool::Fill(FillTool::Check));
h.key_press(egui::Key::Escape);
h.run_steps(2);
assert_eq!(h.state().quick_tool, QuickTool::Select);
}
/// A box opened while a comment is selected deselects it: two presses of Escape still close the
/// box and leave the tool.
#[test]
fn escape_leaves_the_text_tool_in_two_presses_after_a_selection() {
let mut h = harness();
h.state_mut().execute("sign.fill.text");
click(&mut h, 50.0, 250.0);
h.event(egui::Event::Text("Ada".into()));
h.run_steps(1);
commit_typed_text(&mut h);
assert_eq!(items(&h).len(), 1, "{:?}", items(&h));
h.state_mut().views[0].fill_text = None;
h.state_mut().views[0].comments.selected = Some((0, 0));
click(&mut h, 50.0, 150.0);
assert!(h.state().views[0].fill_text.is_some(), "a box is open");
assert_eq!(h.state().views[0].comments.selected, None, "the new box deselected the comment");
h.key_press(egui::Key::Escape);
h.run_steps(2);
assert!(h.state().views[0].fill_text.is_none(), "the first Escape closes the box");
h.key_press(egui::Key::Escape);
h.run_steps(2);
assert_eq!(h.state().quick_tool, QuickTool::Select, "the second Escape leaves the tool");
}
#[test]
fn fill_text_size_controls_new_text_and_survives_settings_restore() {
let mut h = harness();