From e90c92124c1695dc87780f763f70a2556e63b81e Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 30 Oct 2026 07:31:31 -0600 Subject: [PATCH] M5.7: the quick bar's Fill & Sign button shows the tool it picks The button showed a generic pen until clicked, then swapped to Add text's icon. It now works like the Comment, Highlight and Draw groups: it shows the last Fill & Sign tool used (Signature at first) and clicking it picks that tool. The flyout marks the active saved signature or initials card, and the cards get the pointing-hand cursor like the other rows. --- crates/ui-egui/src/app.rs | 1 + crates/ui-egui/src/app/control.rs | 9 ++++-- crates/ui-egui/src/commands.rs | 1 + crates/ui-egui/src/dialogs.rs | 1 + crates/ui-egui/src/hotbar.rs | 16 +++++++++-- crates/ui-egui/src/lib.rs | 1 + crates/ui-egui/src/tools/fill_sign.rs | 13 ++++++--- crates/ui-egui/tests/it/comments.rs | 2 +- crates/ui-egui/tests/it/fill_sign.rs | 40 +++++++++++++++++++++++---- 9 files changed, 69 insertions(+), 15 deletions(-) diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index 6ac8ea8783..bb1748f705 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -108,6 +108,7 @@ impl PdfCraftApp { ui_scale_applied: None, flatten_fill_sign_on_save: false, fill_text_size: fill_sign::TEXT_SIZE, + fill_tool: fill_sign::FillTool::Signature, fill_text_color: [0.0; 3], dialog: None, updates: updates::Updates::default(), diff --git a/crates/ui-egui/src/app/control.rs b/crates/ui-egui/src/app/control.rs index 0309042bf1..fee5378b6e 100644 --- a/crates/ui-egui/src/app/control.rs +++ b/crates/ui-egui/src/app/control.rs @@ -333,9 +333,12 @@ impl PdfCraftApp { field if field.starts_with("field-") => QuickTool::Field( prepare::FieldTool::from_command(&format!("form.add.{}", &field[6..])).ok_or_else(|| format!("unknown tool {field}"))?, ), - fill if fill.starts_with("fill-") => QuickTool::Fill( - fill_sign::FillTool::from_command(&format!("sign.fill.{}", &fill[5..])).ok_or_else(|| format!("unknown tool {fill}"))?, - ), + fill if fill.starts_with("fill-") => { + let f = + fill_sign::FillTool::from_command(&format!("sign.fill.{}", &fill[5..])).ok_or_else(|| format!("unknown tool {fill}"))?; + self.fill_tool = f; + QuickTool::Fill(f) + } other => { let t = comments::CommentTool::from_command(&format!("comment.{other}")).ok_or_else(|| format!("unknown tool {other}"))?; self.comment_prefs.group_tool[t.group()] = t; diff --git a/crates/ui-egui/src/commands.rs b/crates/ui-egui/src/commands.rs index f072b7febe..4fdec5af8a 100644 --- a/crates/ui-egui/src/commands.rs +++ b/crates/ui-egui/src/commands.rs @@ -697,6 +697,7 @@ impl PdfCraftApp { } fill if crate::fill_sign::FillTool::from_command(fill).is_some() => { let Some(tool) = crate::fill_sign::FillTool::from_command(fill) else { return Some(false) }; + self.fill_tool = tool; self.quick_tool = crate::QuickTool::Fill(tool); let initials = tool == crate::fill_sign::FillTool::Initials; if (tool == crate::fill_sign::FillTool::Signature && self.signature.is_none()) || (initials && self.initials.is_none()) { diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 048d519222..8105a22342 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1349,6 +1349,7 @@ fn dialog_signature(ui: &mut egui::Ui, app: &mut PdfCraftApp, req: &mut Pending) app.signature = d.saved(); crate::fill_sign::FillTool::Signature }; + app.fill_tool = tool; app.quick_tool = crate::QuickTool::Fill(tool); app.toast = None; } diff --git a/crates/ui-egui/src/hotbar.rs b/crates/ui-egui/src/hotbar.rs index c66b7e30b0..bbd98be2ef 100644 --- a/crates/ui-egui/src/hotbar.rs +++ b/crates/ui-egui/src/hotbar.rs @@ -239,8 +239,20 @@ fn core_tool_buttons(ui: &mut egui::Ui, app: &mut PdfCraftApp, cell: f32, popup_ QuickTool::Fill(f) => Some(f), _ => None, }; - let shown = current_fill.unwrap_or(crate::fill_sign::FillTool::Text); - let resp = icons::button(ui, if current_fill.is_some() { shown.icon() } else { "pen-line" }, cell, current_fill.is_some(), tl!("Fill & Sign")); + let shown = app.fill_tool; + let tip = match shown { + crate::fill_sign::FillTool::Signature | crate::fill_sign::FillTool::Initials => { + let initials = shown == crate::fill_sign::FillTool::Initials; + let saved = if initials { app.initials.is_some() } else { app.signature.is_some() }; + crate::i18n::fmt( + tl!(if saved { "Place saved {what}" } else { "Add {what}" }), + &[("what", tl!(if initials { "initials" } else { "signature" }))], + ) + } + _ => tl!(shown.label()).to_string(), + }; + let tip = format!("{}: {tip}", tl!("Fill & Sign")); + let resp = icons::button(ui, shown.icon(), cell, current_fill.is_some(), &tip); let r = resp.rect; let tri = [r.right_bottom() + vec2(-4.0, -4.0), r.right_bottom() + vec2(-9.0, -4.0), r.right_bottom() + vec2(-4.0, -9.0)]; ui.painter().add(egui::Shape::convex_polygon(tri.to_vec(), if current_fill.is_some() { t.accent } else { t.text_faint }, Stroke::NONE)); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 643d5e90ff..ae89f96bcb 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -331,6 +331,7 @@ pub struct PdfCraftApp { pub flatten_fill_sign_on_save: bool, /// Point size for new Fill & Sign text and dates, kept across launches. pub fill_text_size: f64, + pub fill_tool: fill_sign::FillTool, /// Colour for new Fill & Sign text and dates, kept across launches. pub fill_text_color: [f64; 3], pub dialog: Option, diff --git a/crates/ui-egui/src/tools/fill_sign.rs b/crates/ui-egui/src/tools/fill_sign.rs index 045bb64974..9beaa9b834 100644 --- a/crates/ui-egui/src/tools/fill_sign.rs +++ b/crates/ui-egui/src/tools/fill_sign.rs @@ -373,20 +373,22 @@ pub(crate) fn signature_entries(ui: &mut egui::Ui, app: &mut crate::PdfCraftApp, } continue; }; - ui.horizontal(|ui| { + let on = app.quick_tool == crate::QuickTool::Fill(if i == 0 { FillTool::Signature } else { FillTool::Initials }); + let wash = ui.painter().add(egui::Shape::Noop); + let row = ui.horizontal(|ui| { let (rect, response) = ui.allocate_exact_size(vec2((ui.available_width() - 68.0).max(140.0), 52.0), Sense::click()); let use_label = label("Use {what}"); - response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, use_label.clone())); + response.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, true, on, use_label.clone())); let painter = ui.painter_at(rect); painter.rect_filled(rect, CornerRadius::same(4), Color32::WHITE); painter.rect_stroke( rect, CornerRadius::same(4), - Stroke::new(1.0, if response.hovered() { t.accent } else { t.border }), + Stroke::new(1.0, if on || response.hovered() { t.accent } else { t.border }), egui::StrokeKind::Inside, ); paint_saved(ui, rect.shrink(8.0), saved, &mut app.saved_signature_previews[i], &format!("saved-{what}")); - if response.on_hover_text(label("Place saved {what}")).clicked() { + if crate::theme::hand(response).on_hover_text(label("Place saved {what}")).clicked() { command = Some(use_id); } if crate::icons::button(ui, "pencil", 26.0, false, &label("Change {what}")).clicked() { @@ -396,6 +398,9 @@ pub(crate) fn signature_entries(ui: &mut egui::Ui, app: &mut crate::PdfCraftApp, command = Some(remove_id); } }); + if on { + ui.painter().set(wash, egui::Shape::rect_filled(row.response.rect.expand(3.0), CornerRadius::same(6), t.accent_soft)); + } ui.add_space(4.0); } command diff --git a/crates/ui-egui/tests/it/comments.rs b/crates/ui-egui/tests/it/comments.rs index 939de58dff..51b9eabce1 100644 --- a/crates/ui-egui/tests/it/comments.rs +++ b/crates/ui-egui/tests/it/comments.rs @@ -3280,7 +3280,7 @@ fn hotbar_docked_left_does_not_override_quick_tools() { }); h.run_steps(5); // Controls in the docked bar share one layout, so none may overlap (#5890). - let labels = ["Drag hotbar", "Select (V)", "Hand (H)", "Add a sticky note", "Highlight", "Draw", "Fill & Sign", "Hotbar settings"]; + let labels = ["Drag hotbar", "Select (V)", "Hand (H)", "Add a sticky note", "Highlight", "Draw", "Fill & Sign: Add signature", "Hotbar settings"]; let rects: Vec<_> = labels.iter().map(|&label| h.get_by_label(label).rect()).collect(); for (i, a) in rects.iter().enumerate() { for (j, b) in rects.iter().enumerate().skip(i + 1) { diff --git a/crates/ui-egui/tests/it/fill_sign.rs b/crates/ui-egui/tests/it/fill_sign.rs index ce21bd9f74..503080e71c 100644 --- a/crates/ui-egui/tests/it/fill_sign.rs +++ b/crates/ui-egui/tests/it/fill_sign.rs @@ -2,7 +2,7 @@ use egui::{Pos2, pos2}; use egui_kittest::Harness; -use egui_kittest::kittest::Queryable; +use egui_kittest::kittest::{NodeT, Queryable}; use pdfcraft_ui_egui::fill_sign::{FillTool, SavedSig}; use pdfcraft_ui_egui::{Dialog, PdfCraftApp, QuickTool}; @@ -423,9 +423,9 @@ fn changing_saved_signatures_and_initials_preserves_placed_marks() { let original = format!("{:?}", h.state().session.get(h.state().views[0].id).unwrap().info.annotations); // The quick toolbar exposes replacement without changing the normal placement action. - h.get_by_label("Fill & Sign").click(); + h.get_by_label("Fill & Sign: Place saved signature").click(); h.run_steps(2); - h.get_by_label("Fill & Sign").click(); + h.get_by_label("Fill & Sign: Place saved signature").click(); h.run_steps(2); h.get_by_label("Change signature").click(); h.run_steps(2); @@ -445,9 +445,9 @@ fn changing_saved_signatures_and_initials_preserves_placed_marks() { click(&mut h, 40.0, 200.0); assert_eq!(items(&h).len(), 2); - h.get_by_label("Fill & Sign").click(); + h.get_by_label("Fill & Sign: Place saved signature").click(); h.run_steps(2); - h.get_by_label("Fill & Sign").click(); + h.get_by_label("Fill & Sign: Place saved signature").click(); h.run_steps(2); h.get_by_label("Change initials").click(); h.run_steps(2); @@ -1812,3 +1812,33 @@ fn mode_tabs_keep_the_hand_over_a_type_box_scrolled_under_them() { h.run_steps(3); assert_eq!(h.state().mode, pdfcraft_ui_egui::Mode::Convert, "the tab takes the click, not the text box"); } + +#[test] +fn the_quick_bar_fill_button_shows_the_tool_it_picks() { + let mut h = harness(); + h.get_by_label("Fill & Sign: Add signature").click(); + h.run_steps(2); + assert_eq!(h.state().quick_tool, QuickTool::Fill(FillTool::Signature)); + assert_eq!(h.state().dialog, Some(Dialog::Signature)); + + h.state_mut().dialog = None; + h.state_mut().signature = Some(SavedSig::Typed("Ada Lovelace".into())); + h.state_mut().quick_tool = QuickTool::Select; + h.run_steps(2); + h.get_by_label("Fill & Sign: Place saved signature").click(); + h.run_steps(2); + assert_eq!(h.state().quick_tool, QuickTool::Fill(FillTool::Signature)); + assert_eq!(h.state().dialog, None); + h.get_by_label("Fill & Sign: Place saved signature").click(); + h.run_steps(2); + assert_eq!(h.get_by_label("Use signature").accesskit_node().toggled(), Some(egui::accesskit::Toggled::True), "the active tool's card is marked"); + h.key_press(egui::Key::Escape); + h.run_steps(2); + + h.state_mut().execute("sign.fill.check"); + h.state_mut().quick_tool = QuickTool::Select; + h.run_steps(2); + h.get_by_label("Fill & Sign: Checkmark").click(); + h.run_steps(2); + assert_eq!(h.state().quick_tool, QuickTool::Fill(FillTool::Check), "the button keeps the last Fill & Sign tool"); +}