mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 02:58:32 -06:00
fix(ui): a tap on an open field or paragraph editor is not a click away
The form field editors and the Edit text paragraph editor are drawn over the pages, outside the page pointer, and close on a click away. With a finger, or a pen on Windows (winit reports it as touch), they closed on taps inside them: - A tap on a combo box opened its list and closed it in the same frame, so a choice could not be picked by touch. - A tap on a list box row picked it, but the list lost focus, so the arrow keys no longer moved the choice. - A tap on a date field's calendar arrows closed the field. - With a paragraph open in Edit text, a tap on a Format text control that only opens a list, such as Line spacing, applied and closed the paragraph, and the list went with it. Two things were wrong. The combo and list boxes judged the click by hover_pos, and the paragraph's "outside the document" by latest_pos. Both are None on the frame egui reports a tap, since egui-winit sends PointerGone with every touch release. They now read interact_pos, which keeps the tap's position on that frame (egui's own clicked_elsewhere does the same). And egui 0.36 reports a lost focus for two frames. The text field editor and the paragraph editor judged their exceptions again on the second frame, when a tap has no position left and a mouse may have moved on. They now judge a loss once, on its own frame (edit_text_ui::lost_focus_once, with a flag kept per editor). A focus taken later in a frame is still seen on the next one, as before. Tests: tests/it/editor_taps.rs taps with support::input::touch_tap, which sends the release and PointerGone in one frame as egui-winit does, and pairs each tap with a mouse click as the control. That click leaves the pointer in the window: kittest's drop_at sends PointerGone a frame later, which the second-frame judging also turned into a click away. With the source reverted, the four tap tests fail at their bug assertion and the controls pass. Reverting one part at a time fails only its own tests: hover_pos in the combo and list check, egui's lost_focus in the field editor (the calendar) or in the paragraph editor, and latest_pos for the paragraph's outside. A tap inside an open text field already worked and has a guard test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1bda33975d
commit
9f2e1e3293
@@ -246,6 +246,9 @@ pub struct LineEditor {
|
||||
size: f32,
|
||||
/// Take the keyboard focus on the next frame.
|
||||
pub(crate) focus: bool,
|
||||
/// Whether the text box had the keyboard at the end of the editor's last frame
|
||||
/// ([`lost_focus_once`]).
|
||||
had_focus: bool,
|
||||
/// The Format text panel's values, and what the paragraph had (to send only changes).
|
||||
pub look: pdfcraft_engine::AddedText,
|
||||
look0: pdfcraft_engine::AddedText,
|
||||
@@ -1196,6 +1199,7 @@ fn open_editor(
|
||||
max_width,
|
||||
size: (l.size as f32 * scale).clamp(8.0, 72.0),
|
||||
focus: true,
|
||||
had_focus: false,
|
||||
look: look_of(l),
|
||||
look0: look_of(l),
|
||||
family_chosen: false,
|
||||
@@ -1556,8 +1560,9 @@ fn editor_galley(ui: &egui::Ui, ed: &LineEditor, text: &str, scale: f32, width:
|
||||
|
||||
/// The inline editor; returns the edit once the text is applied.
|
||||
pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) -> Option<Edit> {
|
||||
// Clicks outside the document (the Format text panel) keep the paragraph open.
|
||||
let outside = ctx.input(|i| i.pointer.latest_pos()).is_some_and(|p| !view.viewport_rect().contains(p));
|
||||
// Clicks outside the document (the Format text panel) keep the paragraph open. Where the
|
||||
// click landed is `interact_pos`: `latest_pos` is gone on the frame a tap lifts.
|
||||
let outside = ctx.input(|i| i.pointer.interact_pos()).is_some_and(|p| !view.viewport_rect().contains(p));
|
||||
let page = view.line_editor.as_ref()?.page;
|
||||
let xf = view.page_xform(page)?;
|
||||
let hold = view.objects.hold_editor;
|
||||
@@ -1703,11 +1708,12 @@ pub(crate) fn overlay(ctx: &egui::Context, view: &mut DocView, info: &DocInfo) -
|
||||
r.request_focus();
|
||||
ed.focus = false;
|
||||
}
|
||||
let lost = lost_focus_once(&r, &mut ed.had_focus);
|
||||
let esc = ui.input(|i| i.key_pressed(egui::Key::Escape));
|
||||
let apply = ui.input(|i| i.key_pressed(egui::Key::Enter) && i.modifiers.command);
|
||||
if esc {
|
||||
done = Some(false);
|
||||
} else if apply || (r.lost_focus() && !outside && !hold) {
|
||||
} else if apply || (lost && !outside && !hold) {
|
||||
done = Some(true);
|
||||
}
|
||||
});
|
||||
@@ -1742,6 +1748,16 @@ pub(crate) fn finished_edit(ed: LineEditor) -> Option<Edit> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether an editor's text box `r` lost the keyboard this frame, given whether it `had` it at
|
||||
/// the end of the editor's last frame (updated here). egui's `lost_focus` holds for a second
|
||||
/// frame as well, and an editor that judges a click away by where the click landed must judge it
|
||||
/// once, on the click's frame: on the next, a tap's position is gone (egui-winit sends
|
||||
/// `PointerGone` with every touch release) and a mouse may have moved on.
|
||||
pub(crate) fn lost_focus_once(r: &egui::Response, had: &mut bool) -> bool {
|
||||
let has = r.has_focus();
|
||||
std::mem::replace(had, has) && !has
|
||||
}
|
||||
|
||||
/// Caret colour that stays visible on a field filled with `fill`: black on light fills, white on dark ones.
|
||||
pub(crate) fn caret_color_on(fill: Color32) -> Color32 {
|
||||
let luma = 299 * u32::from(fill.r()) + 587 * u32::from(fill.g()) + 114 * u32::from(fill.b());
|
||||
@@ -1799,6 +1815,7 @@ mod tests {
|
||||
max_width: 400.0,
|
||||
size: block.size as f32,
|
||||
focus: true,
|
||||
had_focus: false,
|
||||
look: look_of(&block),
|
||||
look0: look_of(&block),
|
||||
family_chosen: false,
|
||||
|
||||
@@ -1609,6 +1609,7 @@ mod revert_draft_tests {
|
||||
calendar: None,
|
||||
calendar_rect: None,
|
||||
calendar_text: None,
|
||||
had_focus: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ pub struct Focus {
|
||||
/// The text the calendar last saw, so it moves to a typed date once, when the typing becomes
|
||||
/// a whole date, and otherwise keeps the month its arrows turned to.
|
||||
pub calendar_text: Option<String>,
|
||||
/// Whether the text box had the keyboard at the end of the editor's last frame
|
||||
/// ([`crate::edit_text_ui::lost_focus_once`]).
|
||||
pub(crate) had_focus: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@@ -121,6 +124,7 @@ fn open_focus(f: &FormField, widget: usize) -> Focus {
|
||||
calendar: None,
|
||||
calendar_rect: None,
|
||||
calendar_text: None,
|
||||
had_focus: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -652,6 +656,10 @@ pub(crate) fn overlay(
|
||||
return None;
|
||||
};
|
||||
let rect = widget_rect(&xf, info, page, w.rect);
|
||||
// Whether a click this frame landed outside the field. Where it landed is `interact_pos`: a
|
||||
// tap's release comes with `PointerGone` (egui-winit sends one with every touch release, and
|
||||
// winit reports Windows pen input as touch), which clears `hover_pos`.
|
||||
let clicked_outside = || ctx.input(|i| i.pointer.any_click() && !i.pointer.interact_pos().is_some_and(|p| rect.contains(p)));
|
||||
let zoom = xf.zoom();
|
||||
let t = Tokens::get(ctx);
|
||||
let mut edit = None;
|
||||
@@ -738,6 +746,7 @@ pub(crate) fn overlay(
|
||||
st.store(ui.ctx(), r.id);
|
||||
}
|
||||
}
|
||||
let lost = crate::edit_text_ui::lost_focus_once(&r, &mut fx.had_focus);
|
||||
let (enter, esc, tab, shift) = ui.input(|i| {
|
||||
(i.key_pressed(egui::Key::Enter), i.key_pressed(egui::Key::Escape), i.key_pressed(egui::Key::Tab), i.modifiers.shift)
|
||||
});
|
||||
@@ -745,7 +754,7 @@ pub(crate) fn overlay(
|
||||
close = true;
|
||||
} else if tab {
|
||||
next = Some(!shift);
|
||||
} else if r.lost_focus()
|
||||
} else if lost
|
||||
&& (!multiline || !enter)
|
||||
&& !fx.calendar_rect.is_some_and(|c| ui.input(|i| i.pointer.interact_pos().is_some_and(|p| c.contains(p))))
|
||||
{
|
||||
@@ -793,9 +802,7 @@ pub(crate) fn overlay(
|
||||
});
|
||||
});
|
||||
});
|
||||
let clicked_away = ctx.input(|i| i.pointer.any_click())
|
||||
&& !resp.response.contains_pointer()
|
||||
&& !ctx.input(|i| i.pointer.hover_pos()).is_some_and(|p| rect.contains(p));
|
||||
let clicked_away = !resp.response.contains_pointer() && clicked_outside();
|
||||
let (tab, shift) = ctx.input(|i| (i.key_pressed(egui::Key::Tab), i.modifiers.shift));
|
||||
if ctx.input(|i| i.key_pressed(egui::Key::Escape)) {
|
||||
close = true;
|
||||
@@ -829,8 +836,7 @@ pub(crate) fn overlay(
|
||||
edit = Some(Edit::SetFieldValue { name: f.name.clone(), value: FieldValue::Choice(vec![export]) });
|
||||
}
|
||||
}
|
||||
let clicked_away = ctx.input(|i| i.pointer.any_click()) && !ctx.input(|i| i.pointer.hover_pos()).is_some_and(|p| rect.contains(p));
|
||||
if clicked_away {
|
||||
if clicked_outside() {
|
||||
close = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
//! A finger tap, or a pen tap on Windows, on an editor open over the page acts as a mouse click
|
||||
//! there. The field editors and the Edit text paragraph editor are drawn over the pages, outside
|
||||
//! the page pointer, and close on a click away. On the frame egui reports a tap there is no
|
||||
//! hover position (egui-winit sends `PointerGone` with every touch release), so they must judge
|
||||
//! where the click landed by the click's own position.
|
||||
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use pdfcraft_ui_egui::PdfCraftApp;
|
||||
|
||||
use crate::support::input::touch_tap;
|
||||
|
||||
/// A PDF of `objects`, numbered from 1 with the catalog first.
|
||||
fn pdf(objects: &[&str]) -> Vec<u8> {
|
||||
let mut out = b"%PDF-1.7\n".to_vec();
|
||||
let mut offsets = Vec::new();
|
||||
for (i, o) in objects.iter().enumerate() {
|
||||
offsets.push(out.len());
|
||||
out.extend_from_slice(format!("{} 0 obj\n{o}\nendobj\n", i + 1).as_bytes());
|
||||
}
|
||||
let xref = out.len();
|
||||
out.extend_from_slice(format!("xref\n0 {}\n0000000000 65535 f \n", objects.len() + 1).as_bytes());
|
||||
for o in offsets {
|
||||
out.extend_from_slice(format!("{o:010} 00000 n \n").as_bytes());
|
||||
}
|
||||
out.extend_from_slice(format!("trailer\n<< /Size {} /Root 1 0 R >>\nstartxref\n{xref}\n%%EOF\n", objects.len() + 1).as_bytes());
|
||||
out
|
||||
}
|
||||
|
||||
const PAGES: &str = "<< /Type /Pages /Kids [3 0 R] /Count 1 /MediaBox [0 0 300 400] >>";
|
||||
const HELVETICA: &str = "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>";
|
||||
|
||||
/// A 300×400 page with a text field `name`, a combo box `country` and a list box `colour`
|
||||
/// (Red, Green, Blue) 70 pt tall.
|
||||
fn form_pdf() -> Vec<u8> {
|
||||
pdf(&[
|
||||
"<< /Type /Catalog /Pages 2 0 R /AcroForm << /Fields [4 0 R 5 0 R 6 0 R] /DA (/Helv 10 Tf 0 g) /DR << /Font << /Helv 7 0 R >> >> >> >>",
|
||||
PAGES,
|
||||
"<< /Type /Page /Parent 2 0 R /Annots [4 0 R 5 0 R 6 0 R] >>",
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Tx /T (name) /Rect [40 340 260 360] /P 3 0 R /F 4 >>",
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Ch /Ff 131072 /T (country) /Opt [[(ca) (Canada)] [(fr) (France)] [(no) (Norway)]] /Rect [40 290 260 310] /P 3 0 R /F 4 >>",
|
||||
"<< /Type /Annot /Subtype /Widget /FT /Ch /T (colour) /Opt [(Red) (Green) (Blue)] /Rect [40 150 200 220] /P 3 0 R /F 4 >>",
|
||||
HELVETICA,
|
||||
])
|
||||
}
|
||||
|
||||
/// A 300×400 page with one line of 12 pt text at x 40, y 300.
|
||||
fn text_pdf() -> Vec<u8> {
|
||||
let line = "BT /F1 12 Tf 40 300 Td (Signed and approved by the committee.) Tj ET";
|
||||
let content = format!("<< /Length {} >>\nstream\n{line}\nendstream", line.len() + 1);
|
||||
pdf(&[
|
||||
"<< /Type /Catalog /Pages 2 0 R >>",
|
||||
PAGES,
|
||||
"<< /Type /Page /Parent 2 0 R /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>",
|
||||
content.as_str(),
|
||||
HELVETICA,
|
||||
])
|
||||
}
|
||||
|
||||
/// The app with `pdf` open at 150 % and its first frames run.
|
||||
fn open(pdf: Vec<u8>) -> Harness<'static, PdfCraftApp> {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(move |_cc| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
app.open_bytes("test.pdf", None, pdf).unwrap();
|
||||
app.set_option("left", "closed").unwrap();
|
||||
app.set_option("zoom", "150").unwrap();
|
||||
app
|
||||
});
|
||||
for _ in 0..30 {
|
||||
h.run_steps(2);
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
/// A mouse click: move, press and release in frames of their own, and the pointer stays there.
|
||||
/// (`drop_at` also sends `PointerGone` a frame later, as if the mouse left the window.)
|
||||
fn mouse_click(h: &mut Harness<'_, PdfCraftApp>, p: egui::Pos2) {
|
||||
h.hover_at(p);
|
||||
h.run_steps(1);
|
||||
h.drag_at(p);
|
||||
h.run_steps(1);
|
||||
h.event(egui::Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: false, modifiers: egui::Modifiers::NONE });
|
||||
h.run_steps(4);
|
||||
}
|
||||
|
||||
/// Where field `name`'s widget is on screen.
|
||||
fn field(h: &Harness<'_, PdfCraftApp>, name: &str) -> egui::Rect {
|
||||
let s = h.state();
|
||||
let doc = s.session.get(s.views[0].id).unwrap();
|
||||
let f = doc.form.iter().find(|f| f.name == name).unwrap();
|
||||
pdfcraft_ui_egui::forms_ui::field_screen_rect(&s.views[0], &doc.info, f, 0).expect("on screen")
|
||||
}
|
||||
|
||||
fn value(h: &Harness<'_, PdfCraftApp>, name: &str) -> Vec<String> {
|
||||
let s = h.state();
|
||||
s.session.get(s.views[0].id).unwrap().form.iter().find(|f| f.name == name).unwrap().value.clone()
|
||||
}
|
||||
|
||||
/// The field whose editor or list is open.
|
||||
fn focus(h: &Harness<'_, PdfCraftApp>) -> Option<String> {
|
||||
h.state().views[0].forms.focus.as_ref().map(|f| f.name.clone())
|
||||
}
|
||||
|
||||
/// The Green row of the list box: the second, rows being 11.5 pt under a 2 pt inset.
|
||||
fn green(h: &Harness<'_, PdfCraftApp>) -> egui::Pos2 {
|
||||
let r = field(h, "colour");
|
||||
egui::pos2(r.center().x, r.min.y + 19.0 * r.height() / 70.0)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Form fields.
|
||||
|
||||
#[test]
|
||||
fn mouse_click_opens_a_combo_box() {
|
||||
// Control for the tap below.
|
||||
let mut h = open(form_pdf());
|
||||
let p = field(&h, "country").center();
|
||||
mouse_click(&mut h, p);
|
||||
assert_eq!(focus(&h).as_deref(), Some("country"), "a click opens the list of choices");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touch_tap_opens_a_combo_box_and_picks_a_choice() {
|
||||
let mut h = open(form_pdf());
|
||||
let p = field(&h, "country").center();
|
||||
touch_tap(&mut h, p);
|
||||
assert_eq!(focus(&h).as_deref(), Some("country"), "a tap opens the list of choices, as a click does");
|
||||
let france = h.get_by_label("France").rect().center();
|
||||
touch_tap(&mut h, france);
|
||||
assert_eq!(value(&h, "country"), ["fr"], "and a tap on a choice picks it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_click_in_a_list_box_keeps_it_focused() {
|
||||
// Control for the tap below.
|
||||
let mut h = open(form_pdf());
|
||||
let p = green(&h);
|
||||
mouse_click(&mut h, p);
|
||||
assert_eq!((value(&h, "colour"), focus(&h).as_deref()), (vec!["Green".to_string()], Some("colour")), "a click picks the row");
|
||||
h.key_press(egui::Key::ArrowDown);
|
||||
h.run_steps(3);
|
||||
assert_eq!(value(&h, "colour"), ["Blue"], "and the list keeps the keyboard");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touch_tap_in_a_list_box_keeps_it_focused() {
|
||||
let mut h = open(form_pdf());
|
||||
let p = green(&h);
|
||||
touch_tap(&mut h, p);
|
||||
assert_eq!(
|
||||
(value(&h, "colour"), focus(&h).as_deref()),
|
||||
(vec!["Green".to_string()], Some("colour")),
|
||||
"a tap picks the row and keeps the list focused, as a click does"
|
||||
);
|
||||
h.key_press(egui::Key::ArrowDown);
|
||||
h.run_steps(3);
|
||||
assert_eq!(value(&h, "colour"), ["Blue"], "so the arrow keys go on from it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touch_tap_inside_an_open_text_field_keeps_typing_there() {
|
||||
// A guard more than a probe: the text editor closes when egui takes its focus away, and
|
||||
// egui judges that from the tap's own position.
|
||||
let mut h = open(form_pdf());
|
||||
let r = field(&h, "name");
|
||||
touch_tap(&mut h, r.center());
|
||||
assert_eq!(focus(&h).as_deref(), Some("name"), "a tap opens the field for typing");
|
||||
touch_tap(&mut h, r.left_center() + egui::vec2(6.0, 0.0));
|
||||
assert_eq!(focus(&h).as_deref(), Some("name"), "a tap inside the open field keeps it open");
|
||||
h.event(egui::Event::Text("Ada".into()));
|
||||
h.run_steps(1);
|
||||
h.key_press(egui::Key::Enter);
|
||||
h.run_steps(3);
|
||||
assert_eq!(value(&h, "name"), ["Ada"], "and the typing lands in it");
|
||||
}
|
||||
|
||||
const MONTHS: [&str; 12] = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
|
||||
/// Adds a date field `due` at x 40..160, y 240..260, its calendar under it.
|
||||
fn add_date_field(h: &mut Harness<'_, PdfCraftApp>) {
|
||||
h.state_mut().apply_edit(pdfcraft_engine::Edit::AddField {
|
||||
page: 0,
|
||||
rect: [40.0, 240.0, 160.0, 260.0],
|
||||
kind: pdfcraft_engine::NewField::Date,
|
||||
name: Some("due".into()),
|
||||
});
|
||||
h.run_steps(4);
|
||||
}
|
||||
|
||||
/// Next month's calendar heading, and its 15th as the date field writes it.
|
||||
fn next_month(h: &Harness<'_, PdfCraftApp>) -> (String, String) {
|
||||
let (y, m, _) = h.state().session.today();
|
||||
let (ny, nm) = if m == 12 { (y + 1, 1) } else { (y, m + 1) };
|
||||
(format!("{} {ny}", MONTHS[(nm - 1) as usize]), format!("{nm:02}/15/{ny}"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_click_on_a_date_fields_calendar_keeps_it_open() {
|
||||
// Control for the tap below.
|
||||
let mut h = open(form_pdf());
|
||||
add_date_field(&mut h);
|
||||
let (heading, picked) = next_month(&h);
|
||||
let p = field(&h, "due").center();
|
||||
mouse_click(&mut h, p);
|
||||
let next = h.get_by_label("›").rect().center();
|
||||
mouse_click(&mut h, next);
|
||||
assert_eq!(focus(&h).as_deref(), Some("due"), "a click on the calendar's next month leaves the field open");
|
||||
h.get_by_label(&heading);
|
||||
let day = h.get_by_label("15").rect().center();
|
||||
mouse_click(&mut h, day);
|
||||
assert_eq!(value(&h, "due"), [picked], "and a click on a day picks it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touch_tap_on_a_date_fields_calendar_keeps_it_open() {
|
||||
let mut h = open(form_pdf());
|
||||
add_date_field(&mut h);
|
||||
let (heading, picked) = next_month(&h);
|
||||
let p = field(&h, "due").center();
|
||||
touch_tap(&mut h, p);
|
||||
let next = h.get_by_label("›").rect().center();
|
||||
touch_tap(&mut h, next);
|
||||
assert_eq!(focus(&h).as_deref(), Some("due"), "a tap on the calendar's next month leaves the field open, as a click does");
|
||||
h.get_by_label(&heading);
|
||||
let day = h.get_by_label("15").rect().center();
|
||||
touch_tap(&mut h, day);
|
||||
assert_eq!(value(&h, "due"), [picked], "and a tap on a day picks it");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// Edit text: a click on the Format text panel keeps the paragraph open. These open the Line
|
||||
// spacing list: a control that changes the paragraph on the click gives it the keyboard back in
|
||||
// that same frame, which hides a click away.
|
||||
|
||||
/// Opens the line of `text_pdf` for typing with Edit text.
|
||||
fn edit_paragraph(h: &mut Harness<'_, PdfCraftApp>) {
|
||||
assert!(h.state_mut().execute("edit.edit_text"));
|
||||
h.run_steps(2);
|
||||
let r = h.state().views[0].page_screen_rect(0).expect("page on screen");
|
||||
let k = r.width() / 300.0;
|
||||
let p = r.min + egui::vec2(60.0 * k, (400.0 - 304.0) * k);
|
||||
mouse_click(h, p);
|
||||
assert!(h.state().views[0].line_editor.is_some(), "the paragraph opens for typing");
|
||||
assert!(h.ctx.memory(|m| m.has_focus(egui::Id::new("edit-text-line-input"))), "with the keyboard");
|
||||
}
|
||||
|
||||
/// The open paragraph's line spacing, if a paragraph is open.
|
||||
fn line_spacing(h: &Harness<'_, PdfCraftApp>) -> Option<f64> {
|
||||
h.state().views[0].line_editor.as_ref().map(|e| e.extras.line_spacing)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mouse_click_on_format_text_keeps_the_paragraph_open() {
|
||||
// Control for the tap below.
|
||||
let mut h = open(text_pdf());
|
||||
edit_paragraph(&mut h);
|
||||
let spacing = h.get_by_value("Auto").rect().center();
|
||||
mouse_click(&mut h, spacing);
|
||||
assert_eq!(line_spacing(&h), Some(0.0), "a click opens the Line spacing list and leaves the paragraph open");
|
||||
let double = h.get_by_label("2").rect().center();
|
||||
mouse_click(&mut h, double);
|
||||
assert_eq!(line_spacing(&h), Some(2.0), "to take the spacing picked");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touch_tap_on_format_text_keeps_the_paragraph_open() {
|
||||
let mut h = open(text_pdf());
|
||||
edit_paragraph(&mut h);
|
||||
let spacing = h.get_by_value("Auto").rect().center();
|
||||
touch_tap(&mut h, spacing);
|
||||
assert_eq!(line_spacing(&h), Some(0.0), "a tap opens the Line spacing list and leaves the paragraph open, as a click does");
|
||||
let double = h.get_by_label("2").rect().center();
|
||||
touch_tap(&mut h, double);
|
||||
assert_eq!(line_spacing(&h), Some(2.0), "to take the spacing picked");
|
||||
}
|
||||
@@ -39,6 +39,7 @@ mod date_format;
|
||||
mod dialog_chrome;
|
||||
mod document_links;
|
||||
mod editing;
|
||||
mod editor_taps;
|
||||
mod export;
|
||||
mod fill_sign;
|
||||
mod folders;
|
||||
|
||||
Reference in New Issue
Block a user