mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 11:37:59 -06:00
Text selection: a drag after a double- or triple-click extends by whole words or lines (fixes #1685) (#1689)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Brandon Thomas
parent
44fbacfffd
commit
f48b76c146
@@ -266,6 +266,16 @@ impl Selection {
|
||||
}
|
||||
}
|
||||
|
||||
/// The glyphs `clicks` quick clicks select around glyph `a`: two the word, three the line, more
|
||||
/// the page.
|
||||
fn click_unit(text: &PageText, clicks: u32, a: usize) -> Option<(usize, usize)> {
|
||||
match clicks {
|
||||
2 => text.word_at(a),
|
||||
3 => text.line_at(a),
|
||||
_ => text.glyphs.len().checked_sub(1).map(|last| (0, last)),
|
||||
}
|
||||
}
|
||||
|
||||
struct PageTex {
|
||||
tag: u64,
|
||||
tex: TextureHandle,
|
||||
@@ -2840,11 +2850,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
// Multi-click highlights immediately on pointer press instead of release (#1024).
|
||||
let clicks = if view.clicks > 2 && tool != QuickTool::Select { 1 } else { view.clicks };
|
||||
if clicks >= 2 && over_text {
|
||||
let span = text.nearest(vx, vy).and_then(|a| match clicks {
|
||||
2 => text.word_at(a),
|
||||
3 => text.line_at(a),
|
||||
_ => text.glyphs.len().checked_sub(1).map(|last| (0, last)),
|
||||
});
|
||||
let span = text.nearest(vx, vy).and_then(|a| click_unit(&text, clicks, a));
|
||||
if let Some((first, last)) = span {
|
||||
view.selection = Some(Selection::run(i, first, last));
|
||||
}
|
||||
@@ -2862,10 +2868,22 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
};
|
||||
}
|
||||
}
|
||||
let clicks = if view.clicks > 2 && tool != QuickTool::Select { 1 } else { view.clicks };
|
||||
if resp.dragged()
|
||||
&& let Some(sel) = view.selection.as_mut().filter(|s| s.page == i)
|
||||
{
|
||||
match &mut sel.span {
|
||||
// After a multi-click the drag extends by its unit (#1685), as in
|
||||
// Acrobat and browsers: whole words or lines, always keeping the one
|
||||
// clicked, which holds the anchor whichever way the drag went.
|
||||
Span::Run { anchor, head } if clicks >= 2 => {
|
||||
if let Some(h) = text.nearest(vx, vy)
|
||||
&& let Some((first, last)) = click_unit(&text, clicks, *anchor)
|
||||
{
|
||||
let (to_first, to_last) = click_unit(&text, clicks, h).unwrap_or((h, h));
|
||||
(*anchor, *head) = if to_first < first { (last, to_first) } else { (first, to_last.max(last)) };
|
||||
}
|
||||
}
|
||||
Span::Run { head, .. } => {
|
||||
if let Some(h) = text.nearest(vx, vy) {
|
||||
*head = h;
|
||||
@@ -2901,11 +2919,7 @@ pub fn document_area(app: &mut PdfCraftApp, index: usize, ui: &mut egui::Ui) {
|
||||
// (a highlight made by the double-click left its word selected).
|
||||
if release_clicks > 0 && clicks >= 2 && over_text {
|
||||
if view.selection.is_none() {
|
||||
let span = text.nearest(vx, vy).and_then(|a| match clicks {
|
||||
2 => text.word_at(a),
|
||||
3 => text.line_at(a),
|
||||
_ => text.glyphs.len().checked_sub(1).map(|last| (0, last)),
|
||||
});
|
||||
let span = text.nearest(vx, vy).and_then(|a| click_unit(&text, clicks, a));
|
||||
if let Some((first, last)) = span {
|
||||
view.selection = Some(Selection::run(i, first, last));
|
||||
}
|
||||
|
||||
@@ -314,3 +314,38 @@ fn a_selection_drag_held_at_the_bottom_edge_scrolls_the_view() {
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().views[0].selected_text(), Some(held), "releasing keeps the selection");
|
||||
}
|
||||
|
||||
/// Click at `at`, then press there again and drag to `to` before releasing: a double-click drag.
|
||||
fn double_click_drag(h: &mut Harness<'static, PdfCraftApp>, at: Pos2, to: Pos2) {
|
||||
click(h, at);
|
||||
press_and_move(h, at, to);
|
||||
h.event(Event::PointerButton { pos: to, button: PointerButton::Primary, pressed: false, modifiers: Modifiers::NONE });
|
||||
h.run_steps(1);
|
||||
}
|
||||
|
||||
/// Issue #1685: dragging after a double-click extends the selection a word at a time, and the
|
||||
/// double-clicked word stays selected whichever way the drag goes.
|
||||
#[test]
|
||||
fn a_double_click_drag_extends_by_whole_words_both_ways() {
|
||||
let (mut h, _) = harness(|_| {});
|
||||
let glyph = |h: &Harness<'static, PdfCraftApp>, g: usize| h.state().views[0].glyph_screen_pos(0, g).expect("glyph on screen");
|
||||
// "The quick brown fox": the "u" of "quick" is glyph 5, the "o" of "brown" glyph 12.
|
||||
let (u, o) = (glyph(&h, 5), glyph(&h, 12));
|
||||
double_click_drag(&mut h, u, o);
|
||||
let right = h.state().views[0].selected_text();
|
||||
h.run_steps(60);
|
||||
double_click_drag(&mut h, o, u);
|
||||
let left = h.state().views[0].selected_text();
|
||||
assert_eq!([right.as_deref(), left.as_deref()], [Some("quick brown"); 2], "dragging right, then left");
|
||||
}
|
||||
|
||||
/// Issue #1685: after a triple-click the drag extends a whole line at a time.
|
||||
#[test]
|
||||
fn a_triple_click_drag_extends_by_whole_lines() {
|
||||
let (mut h, at) = harness(|_| {});
|
||||
// A glyph inside "jumps over the dog", on the second line.
|
||||
let below = h.state().views[0].glyph_screen_pos(0, 23).expect("glyph on screen");
|
||||
click(&mut h, at);
|
||||
double_click_drag(&mut h, at, below);
|
||||
assert_eq!(h.state().views[0].selected_text().as_deref(), Some("The quick brown fox\njumps over the dog"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user