use frus_widgets::{Edit, Point, SelectionHandle};
const TOUCH_TARGET: f32 = 48.0;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Handle {
Start,
End,
}
impl Handle {
pub fn index(self) -> usize {
match self {
Handle::Start => 0,
Handle::End => 1,
}
}
}
pub(crate) fn grab(handles: &[SelectionHandle; 2], at: Point) -> Option<Handle> {
let reach = |handle: &SelectionHandle| {
let centre_x = handle.rect.x + handle.rect.width * 0.5;
let centre_y = handle.rect.y + handle.rect.height * 0.5;
let (dx, dy) = (at.x - centre_x, at.y - centre_y);
let half = TOUCH_TARGET * 0.5;
(dx.abs() <= half && dy.abs() <= half).then_some(dx * dx + dy * dy)
};
match (reach(&handles[0]), reach(&handles[1])) {
(Some(start), Some(end)) if end < start => Some(Handle::End),
(Some(_), _) => Some(Handle::Start),
(None, Some(_)) => Some(Handle::End),
(None, None) => None,
}
}
pub(crate) fn drag(edit: Edit, handle: Handle, to: usize) -> Option<Edit> {
let (start, end) = edit.selection_range()?;
let (fixed, moving) = match handle {
Handle::Start if to < end => (end, to),
Handle::End if to > start => (start, to),
_ => return None,
};
Some(Edit {
cursor: moving,
anchor: Some(fixed),
composing: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use frus_widgets::Rect;
fn pair(from: f32, to: f32) -> [SelectionHandle; 2] {
let handle = |x: f32, start: bool| SelectionHandle {
rect: Rect::new(if start { x - 22.0 } else { x }, 20.0, 22.0, 22.0),
line_center: Point::new(x, 10.0),
};
[handle(from, true), handle(to, false)]
}
fn selected(anchor: usize, cursor: usize) -> Edit {
Edit {
cursor,
anchor: Some(anchor),
composing: None,
}
}
#[test]
fn a_finger_takes_the_handle_it_lands_on() {
let handles = pair(50.0, 150.0);
assert_eq!(grab(&handles, Point::new(39.0, 31.0)), Some(Handle::Start));
assert_eq!(grab(&handles, Point::new(161.0, 31.0)), Some(Handle::End));
assert_eq!(
grab(&handles, Point::new(39.0, 31.0 + 20.0)),
Some(Handle::Start)
);
assert_eq!(grab(&handles, Point::new(39.0, 31.0 + 30.0)), None);
assert_eq!(grab(&handles, Point::new(100.0, 31.0)), None);
}
#[test]
fn when_both_are_in_reach_the_nearer_one_is_taken() {
let handles = pair(100.0, 110.0);
assert_eq!(grab(&handles, Point::new(100.0, 31.0)), Some(Handle::Start));
assert_eq!(grab(&handles, Point::new(110.0, 31.0)), Some(Handle::End));
}
#[test]
fn dragging_a_handle_moves_its_own_end_only() {
for edit in [selected(6, 11), selected(11, 6)] {
let wider = drag(edit, Handle::Start, 3).expect("the start moves left");
assert_eq!(wider.selection_range(), Some((3, 11)));
let shorter = drag(edit, Handle::End, 8).expect("the end moves left");
assert_eq!(shorter.selection_range(), Some((6, 8)));
}
}
#[test]
fn the_end_being_dragged_is_the_caret() {
let edit = drag(selected(6, 11), Handle::Start, 3).expect("a move");
assert_eq!((edit.cursor, edit.anchor), (3, Some(11)));
let edit = drag(selected(6, 11), Handle::End, 9).expect("a move");
assert_eq!((edit.cursor, edit.anchor), (9, Some(6)));
}
#[test]
fn the_ends_never_meet_nor_cross() {
let edit = selected(6, 11);
assert_eq!(drag(edit, Handle::Start, 11), None, "onto the other end");
assert_eq!(drag(edit, Handle::Start, 13), None, "past it");
assert_eq!(drag(edit, Handle::End, 6), None);
assert_eq!(drag(edit, Handle::End, 2), None);
assert_eq!(
drag(edit, Handle::Start, 10).and_then(|e| e.selection_range()),
Some((10, 11))
);
}
#[test]
fn a_composition_does_not_survive_a_handle() {
let mut edit = selected(6, 11);
edit.composing = Some((6, 11));
assert_eq!(drag(edit, Handle::End, 9).map(|e| e.composing), Some(None));
}
#[test]
fn without_a_selection_there_is_nothing_to_drag() {
let caret = Edit {
cursor: 4,
anchor: None,
composing: None,
};
assert_eq!(drag(caret, Handle::End, 8), None);
}
}