mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-14 13:33:35 +00:00
M9: a sign tab stays on the page, and the one painted last takes the click
This commit is contained in:
@@ -428,10 +428,18 @@ fn sign_tab_label(ui: &egui::Ui, size: f32) -> std::sync::Arc<egui::Galley> {
|
||||
ui.fonts_mut(|f| f.layout_no_wrap(tl!("Sign").to_string(), theme::semibold(size), Color32::WHITE))
|
||||
}
|
||||
|
||||
/// The sign tab of an empty signature field whose widget is at `field` on screen, laid out for
|
||||
/// its label measured at its largest size: where it is painted and where a click on it signs.
|
||||
fn sign_tab_at(ui: &egui::Ui, field: Rect) -> Option<SignTab> {
|
||||
sign_tab_layout(field, sign_tab_label(ui, SIGN_TAB_FONT).size().x)
|
||||
/// The sign tab of an empty signature field whose widget is at `field` on screen, on the page at
|
||||
/// `page`, laid out for its label measured at its largest size: where it is painted and where a
|
||||
/// click on it signs.
|
||||
fn sign_tab_at(ui: &egui::Ui, field: Rect, page: Rect) -> Option<SignTab> {
|
||||
sign_tab_on_page(field, page, sign_tab_label(ui, SIGN_TAB_FONT).size().x)
|
||||
}
|
||||
|
||||
/// [`sign_tab_layout`] in the part of `field` that is on `page`: form fields take clicks on the
|
||||
/// page only, so a widget that straddles the page's edge gets its tab where a click reaches it,
|
||||
/// or none when that part has no room for one.
|
||||
pub(crate) fn sign_tab_on_page(field: Rect, page: Rect, label_width: f32) -> Option<SignTab> {
|
||||
sign_tab_layout(field.intersect(page), label_width)
|
||||
}
|
||||
|
||||
/// Paints a sign tab laid out by [`sign_tab_at`]: an accent chip with the signature icon and
|
||||
@@ -3262,7 +3270,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
sign_tabs(&doc.signatures, &form, i, can_fill)
|
||||
.into_iter()
|
||||
.filter_map(|(name, [x0, y0, x1, y1])| {
|
||||
sign_tab_at(ui, xf.user_rect(info, i, [x0 as f32, y0 as f32, x1 as f32, y1 as f32])).map(|tab| (name, tab))
|
||||
sign_tab_at(ui, xf.user_rect(info, i, [x0 as f32, y0 as f32, x1 as f32, y1 as f32]), xf.rect).map(|tab| (name, tab))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
@@ -5692,6 +5700,33 @@ mod sign_tab_tests {
|
||||
assert!(near(tab.rect.height(), 14.4) && tab.rect.width() <= 46.0, "{tab:?}");
|
||||
}
|
||||
|
||||
/// A field that straddles the page's edge gets its tab in the part on the page, where clicks
|
||||
/// reach it, or none when that part has no room for one.
|
||||
#[test]
|
||||
fn a_sign_tab_stays_on_the_page() {
|
||||
let page = Rect::from_min_size(pos2(100.0, 50.0), vec2(600.0, 800.0));
|
||||
let straddling = Rect::from_min_size(pos2(60.0, 20.0), vec2(300.0, 90.0));
|
||||
let tab = sign_tab_on_page(straddling, page, 30.0).expect("the part on the page has room");
|
||||
assert!(page.contains_rect(tab.rect), "{tab:?} on {page:?}");
|
||||
assert_eq!(tab.rect.height(), SIGN_TAB_MAX, "sized to the 60 px on the page");
|
||||
let sliver = Rect::from_min_size(pos2(60.0, 20.0), vec2(300.0, 36.0));
|
||||
assert_eq!(sign_tab_on_page(sliver, page, 30.0), None, "6 px on the page");
|
||||
let off = Rect::from_min_size(pos2(60.0, 900.0), vec2(300.0, 90.0));
|
||||
assert_eq!(sign_tab_on_page(off, page, 30.0), None, "wholly off the page");
|
||||
}
|
||||
|
||||
/// Where two sign tabs overlap, a click takes the one painted last, which is on top.
|
||||
#[test]
|
||||
fn the_sign_tab_painted_last_takes_the_click() {
|
||||
let tab = |x: f32| sign_tab_layout(Rect::from_min_size(pos2(x, 0.0), vec2(300.0, 60.0)), 30.0).expect("fits");
|
||||
let tabs = [("Approver", tab(0.0)), ("Witness", tab(10.0))];
|
||||
let both = tabs[1].1.rect.left_center() + vec2(2.0, 0.0);
|
||||
assert!(tabs[0].1.rect.contains(both), "the tabs overlap there");
|
||||
assert_eq!(crate::forms_ui::tab_under(&tabs, both), Some("Witness"));
|
||||
assert_eq!(crate::forms_ui::tab_under(&tabs, tabs[0].1.rect.left_center() + vec2(2.0, 0.0)), Some("Approver"));
|
||||
assert_eq!(crate::forms_ui::tab_under(&tabs, pos2(-5.0, -5.0)), None);
|
||||
}
|
||||
|
||||
/// One page with three signature fields: Approver is empty, Witness is signed (a signature
|
||||
/// dictionary as its value is enough to list it as signed) and Locked is empty but read-only.
|
||||
const SIGN_FIELDS: &[u8] = b"%PDF-1.7
|
||||
|
||||
@@ -119,6 +119,12 @@ fn widget_at<'a>(xf: &PageXform, info: &DocInfo, page: usize, f: &'a FormField,
|
||||
.map(|(i, w)| (f, i, w))
|
||||
}
|
||||
|
||||
/// The field whose sign tab is under `p`, of `tabs` in the order they are painted: where tabs
|
||||
/// overlap, the one painted last is on top.
|
||||
pub(crate) fn tab_under<'a>(tabs: &[(&'a str, SignTab)], p: egui::Pos2) -> Option<&'a str> {
|
||||
tabs.iter().rev().find(|(_, tab)| tab.rect.contains(p)).map(|(name, _)| *name)
|
||||
}
|
||||
|
||||
/// Clicks on form widgets of one page. The pointer is on the first widget under it in the form's
|
||||
/// order, except on a sign tab (`tabs`, with the signature field each one signs), which is drawn
|
||||
/// over every field and so is on its own field.
|
||||
@@ -137,9 +143,8 @@ pub(crate) fn page_input(
|
||||
let pointer = ui.input(|i| i.pointer.hover_pos());
|
||||
let Some(p) = pointer.filter(|p| xf.rect.contains(*p)) else { return FieldPointer::Away };
|
||||
// A tab lies inside its field's widget, so that widget is under the pointer too.
|
||||
let on_tab = tabs.iter().find(|(_, tab)| tab.rect.contains(p)).and_then(|(name, _)| {
|
||||
form.iter().filter(|f| f.name == *name && f.kind == FormFieldKind::Signature).find_map(|f| widget_at(xf, info, page, f, p))
|
||||
});
|
||||
let on_tab = tab_under(tabs, p)
|
||||
.and_then(|name| form.iter().filter(|f| f.name == name && f.kind == FormFieldKind::Signature).find_map(|f| widget_at(xf, info, page, f, p)));
|
||||
let hit = on_tab.or_else(|| form.iter().find_map(|f| widget_at(xf, info, page, f, p)));
|
||||
let Some((f, wi, w)) = hit else { return FieldPointer::Away };
|
||||
// Empty signature fields are signed by clicking them (Use a certificate); push buttons run
|
||||
|
||||
Reference in New Issue
Block a user