mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:40:14 -06:00
Canvas: keep the last move before the button-up when it is elsewhere
When the release frame held moves after the button-up, the last move before it was dropped as the pointer's position, although the release event carries its own position. That move's sample was lost from the stroke. It is now kept unless it is at the button-up's position, and its tilt and rotation still give the release's pose.
This commit is contained in:
@@ -150,15 +150,20 @@ pub(super) type FrameMove = (Pos2, Option<f32>, Option<PenPose>);
|
||||
/// tilt and rotation the feed kept for it come second, if any. A mouse's moves count too, with no
|
||||
/// pressure: a fast mouse stroke otherwise keeps one position a frame, a sample every few pixels,
|
||||
/// too few for a low Fidelity to follow the hand (#3328). With `release`, the moves stop at that
|
||||
/// button's button-up in this frame, if it came: the moves after it are hover, and the last move
|
||||
/// before it is the pointer's position.
|
||||
/// button's button-up in this frame, if it came: the moves after it are hover, and the button-up's
|
||||
/// position is the pointer's. The last move before it is left out only when it is at that
|
||||
/// position; its tilt and rotation come second either way.
|
||||
pub(super) fn frame_moves(ui: &Ui, stylus: Option<&StylusFeed>, release: Option<egui::PointerButton>) -> (Vec<FrameMove>, Option<PenPose>) {
|
||||
let samples = stylus.map(StylusFeed::take_moves).unwrap_or_default();
|
||||
ui.input(|i| {
|
||||
// How many of the frame's moves came before the button-up, if it came.
|
||||
// How many of the frame's moves came before the button-up, and where it came, if it came.
|
||||
let before_up = release.and_then(|b| {
|
||||
let up = i.events.iter().position(|e| matches!(e, egui::Event::PointerButton { button, pressed: false, .. } if *button == b))?;
|
||||
Some(i.events.iter().take(up).filter(|e| matches!(e, egui::Event::PointerMoved(_))).count())
|
||||
i.events.iter().enumerate().find_map(|(k, e)| match e {
|
||||
egui::Event::PointerButton { pos, button, pressed: false, .. } if *button == b => {
|
||||
Some((i.events.iter().take(k).filter(|e| matches!(e, egui::Event::PointerMoved(_))).count(), *pos))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
let mut moves = vec![];
|
||||
if i.events.iter().any(|e| matches!(e, egui::Event::Touch { .. })) {
|
||||
@@ -181,10 +186,19 @@ pub(super) fn frame_moves(ui: &Ui, stylus: Option<&StylusFeed>, release: Option<
|
||||
moves = i.events.iter().filter_map(|e| if let egui::Event::PointerMoved(p) = e { Some((*p, None, None)) } else { None }).collect();
|
||||
}
|
||||
// Cut after pairing: the feed's samples pair with every move of the frame.
|
||||
if let Some(n) = before_up {
|
||||
moves.truncate(n);
|
||||
}
|
||||
let last_pose = moves.pop().and_then(|(_, _, q)| q);
|
||||
// The pointer's position is the button-up's: a last move before it that is elsewhere
|
||||
// stays a move. Its tilt and rotation are still the pose.
|
||||
let last_pose = match before_up {
|
||||
Some((n, up_at)) => {
|
||||
moves.truncate(n);
|
||||
let pose = moves.last().and_then(|&(_, _, q)| q);
|
||||
if moves.last().is_some_and(|&(p, _, _)| p == up_at) {
|
||||
moves.pop();
|
||||
}
|
||||
pose
|
||||
}
|
||||
None => moves.pop().and_then(|(_, _, q)| q),
|
||||
};
|
||||
// A burst of events (a stalled frame) is thinned to a bounded count.
|
||||
const MAX: usize = 64;
|
||||
if moves.len() > MAX {
|
||||
|
||||
@@ -6636,6 +6636,27 @@ fn frame_moves_stop_at_the_button_up() {
|
||||
assert_eq!(moves, [(a, None, None), (b, None, None), (c, None, None)], "without a release every move counts");
|
||||
}
|
||||
|
||||
/// A button-up away from the last move before it keeps that move: the release takes only its own
|
||||
/// position, and the pose is still the last move's.
|
||||
#[test]
|
||||
fn frame_moves_keep_the_last_move_when_the_button_went_up_elsewhere() {
|
||||
let ctx = egui::Context::default();
|
||||
let (a, b, up_at, c) = (pos2(10.0, 20.0), pos2(20.0, 30.0), pos2(25.0, 35.0), pos2(30.0, 40.0));
|
||||
let up = egui::Event::PointerButton { pos: up_at, button: egui::PointerButton::Primary, pressed: false, modifiers: Default::default() };
|
||||
let events = vec![egui::Event::PointerMoved(a), egui::Event::PointerMoved(b), up, egui::Event::PointerMoved(c)];
|
||||
let feed = StylusFeed::default();
|
||||
let at_b = PenPose { tilt: Some([20.0, 0.0]), rotation: None };
|
||||
for (p, pose) in [(0.4, PenPose::default()), (0.6, at_b), (0.7, PenPose::default())] {
|
||||
feed.set_move(crate::stylus::PenSample { pressure: p, pose, ..Default::default() });
|
||||
}
|
||||
let (mut moves, mut last) = (vec![], None);
|
||||
let release = Some(egui::PointerButton::Primary);
|
||||
let mut out = ctx.run_ui(egui::RawInput { events, ..Default::default() }, |ui| (moves, last) = frame_moves(ui, Some(&feed), release));
|
||||
out.textures_delta.clear();
|
||||
assert_eq!(moves, [(a, Some(0.4), Some(PenPose::default())), (b, Some(0.6), Some(at_b))], "the move before the button-up is kept");
|
||||
assert_eq!(last, Some(at_b), "the pose is the last move's before the button-up");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_moves_keeps_touch_feed_and_mouse_samples_with_their_pressure() {
|
||||
let ctx = egui::Context::default();
|
||||
|
||||
Reference in New Issue
Block a user