mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 05:22:22 +00:00
Merge pull request #5575 from dotCooCoo/fix/backquote-at-release
Canvas: a drag's release reads Backtick as it was at the button-up
This commit is contained in:
@@ -89,6 +89,46 @@ pub(super) fn tool_drag_mods_changed(ui: &Ui, m: Mods, backquote: bool) -> bool
|
||||
dragged && last.is_some_and(|l| l != held)
|
||||
}
|
||||
|
||||
/// Whether Backtick is held at the end of this frame, and whether it was at the end of the frame
|
||||
/// before (`None` before the first). The canvas records it once a frame.
|
||||
pub(super) fn backquote_seen(ui: &Ui) -> (bool, Option<bool>) {
|
||||
let id = egui::Id::unique("canvas-backquote-seen");
|
||||
let now = ui.input(|i| i.key_down(egui::Key::Backtick));
|
||||
let before = ui.data_mut(|d| {
|
||||
let before = d.get_temp::<bool>(id);
|
||||
d.insert_temp(id, now);
|
||||
before
|
||||
});
|
||||
(now, before)
|
||||
}
|
||||
|
||||
/// Whether Backtick was held when `button` went up in this frame, with `before` its state at the end
|
||||
/// of the frame before ([`backquote_seen`]). Its state as the frame began is the opposite of its
|
||||
/// first press or release in the frame; when the window lost the focus first (which lets go of every
|
||||
/// key without a release), it is `before`; with neither, it is its state now. The frame's presses,
|
||||
/// releases and focus losses before the button-up then apply in the order they came. Without a
|
||||
/// button-up in this frame, its state now.
|
||||
pub(super) fn backquote_at_release(ui: &Ui, button: egui::PointerButton, before: Option<bool>) -> bool {
|
||||
ui.input(|i| {
|
||||
let now = i.key_down(egui::Key::Backtick);
|
||||
// A press or release of Backtick (`Some(pressed)`), or a focus loss (`None`).
|
||||
let step = |e: &egui::Event| match e {
|
||||
egui::Event::Key { key: egui::Key::Backtick, pressed, repeat: false, .. } => Some(Some(*pressed)),
|
||||
egui::Event::WindowFocused(false) => Some(None),
|
||||
_ => None,
|
||||
};
|
||||
let Some(up) = i.events.iter().position(|e| matches!(e, egui::Event::PointerButton { button: b, pressed: false, .. } if *b == button)) else {
|
||||
return now;
|
||||
};
|
||||
let start = match i.events.iter().find_map(step) {
|
||||
Some(Some(down)) => !down,
|
||||
Some(None) => before.unwrap_or(now),
|
||||
None => now,
|
||||
};
|
||||
i.events[..up].iter().filter_map(step).fold(start, |_, s| s.unwrap_or(false))
|
||||
})
|
||||
}
|
||||
|
||||
/// The pen pressure (0..1) of the press in progress: the force of this frame's pen or touch
|
||||
/// input, else the pressure in the host's feed while a pen is the pointer that moved last
|
||||
/// ([`crate::Services::stylus`]: [`StylusFeed::press`] for a new press, [`StylusFeed::held`]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use super::artboards::{artboard_name_at, begin_artboard_name_edit, name_press_id, select_named_artboard};
|
||||
use super::drag::{
|
||||
FrameMove, HoverContext, apply_requests, dispatch, drag_art_out, drag_id, drag_layer_id, drag_pos_id, drop_press, mods, move_pose, on_canvas,
|
||||
pen_pose, pen_pressure, tool_drag_doc_id, tool_drag_mods_changed, tool_drag_moved, tool_frame_moves, tool_holds, tool_press_doc_id,
|
||||
FrameMove, HoverContext, apply_requests, backquote_at_release, backquote_seen, dispatch, drag_art_out, drag_id, drag_layer_id, drag_pos_id,
|
||||
drop_press, mods, move_pose, on_canvas, pen_pose, pen_pressure, tool_drag_doc_id, tool_drag_mods_changed, tool_drag_moved, tool_frame_moves,
|
||||
tool_holds, tool_press_doc_id,
|
||||
};
|
||||
use super::view::{
|
||||
HOLD_DELAY, HOLD_ZOOM, SCRUB_SLOP, SCRUB_ZOOM, WheelTurn, animated_zoom, pan_pixels, rotate_about, scroll_view, touch_gestures, trackpad_turn,
|
||||
@@ -19,7 +20,8 @@ pub(super) fn handle_input(app: &mut VectorcraftApp, ui: &Ui, resp: &egui::Respo
|
||||
// double-taps) where it was seen last (#491).
|
||||
let at = hover.or_else(|| pointer.interact_pos().filter(|p| rect.contains(*p)));
|
||||
let view = app.view_info();
|
||||
let f = Pointing { resp, rect, pointer: &pointer, m, space, xf, v, view };
|
||||
let (_, backquote_before) = backquote_seen(ui);
|
||||
let f = Pointing { resp, rect, pointer: &pointer, m, space, xf, v, view, backquote_before };
|
||||
touch_gestures(app, ui, rect, view);
|
||||
// Pressed and held still, a pen or a finger opens the context menu (egui's long touch): the
|
||||
// tool's press is dropped, as a right-click presses nothing.
|
||||
@@ -186,6 +188,8 @@ struct Pointing<'a> {
|
||||
xf: Xf,
|
||||
v: View,
|
||||
view: vectorcraft_engine::ViewInfo,
|
||||
/// Whether Backtick was held at the end of the frame before ([`backquote_seen`]).
|
||||
backquote_before: Option<bool>,
|
||||
}
|
||||
|
||||
/// Zoom around the pointer, and scroll ([`wheel`]); a trackpad rotation turns the view. Returns the pointer, the
|
||||
@@ -433,6 +437,8 @@ fn drag_released(app: &mut VectorcraftApp, ui: &Ui, f: &Pointing, d: Drag, p: Po
|
||||
dd.remove::<Point>(tool_drag_doc_id());
|
||||
dd.remove::<Point>(tool_press_doc_id());
|
||||
});
|
||||
// Backtick as it was at the button-up: a key pressed or released later in the frame does not count.
|
||||
let backquote = backquote_at_release(ui, egui::PointerButton::Primary, f.backquote_before);
|
||||
match d {
|
||||
Drag::ZoomBox { start } => {
|
||||
let start = zoom_marquee_start(ui, start, before, p, space);
|
||||
@@ -472,7 +478,6 @@ fn drag_released(app: &mut VectorcraftApp, ui: &Ui, f: &Pointing, d: Drag, p: Po
|
||||
let (moves, last_pose) = tool_frame_moves(ui, app, true);
|
||||
let pen = move_pose(app, &xf, last_pose);
|
||||
if let Some(last) = last_drag {
|
||||
let backquote = ui.input(|i| i.key_down(egui::Key::Backtick));
|
||||
dispatch_frame_moves(app, ui, f, backquote, moves);
|
||||
if last != xf.to_doc(p) {
|
||||
let ev = PointerEvent {
|
||||
@@ -490,7 +495,7 @@ fn drag_released(app: &mut VectorcraftApp, ui: &Ui, f: &Pointing, d: Drag, p: Po
|
||||
kind: PointerKind::Up,
|
||||
pos: xf.to_doc(p),
|
||||
mods: mods(m, space),
|
||||
backquote: ui.input(|i| i.key_down(egui::Key::Backtick)),
|
||||
backquote,
|
||||
pressure: pen_pressure(ui, app, false),
|
||||
pen,
|
||||
};
|
||||
@@ -501,30 +506,8 @@ fn drag_released(app: &mut VectorcraftApp, ui: &Ui, f: &Pointing, d: Drag, p: Po
|
||||
egui::DragAndDrop::clear_payload(ui.ctx());
|
||||
let (pressure, pen) = (pen_pressure(ui, app, false), pen_pose(app, &xf));
|
||||
let pos = xf.to_doc(p);
|
||||
dispatch(
|
||||
app,
|
||||
&PointerEvent {
|
||||
kind: PointerKind::Drag,
|
||||
pos,
|
||||
mods: mods(m, space),
|
||||
backquote: ui.input(|i| i.key_down(egui::Key::Backtick)),
|
||||
pressure,
|
||||
pen,
|
||||
},
|
||||
view,
|
||||
);
|
||||
dispatch(
|
||||
app,
|
||||
&PointerEvent {
|
||||
kind: PointerKind::Up,
|
||||
pos,
|
||||
mods: mods(m, space),
|
||||
backquote: ui.input(|i| i.key_down(egui::Key::Backtick)),
|
||||
pressure,
|
||||
pen,
|
||||
},
|
||||
view,
|
||||
);
|
||||
dispatch(app, &PointerEvent { kind: PointerKind::Drag, pos, mods: mods(m, space), backquote, pressure, pen }, view);
|
||||
dispatch(app, &PointerEvent { kind: PointerKind::Up, pos, mods: mods(m, space), backquote, pressure, pen }, view);
|
||||
}
|
||||
Drag::Art => {
|
||||
// Released over a panel: the move goes back. (The app ends it before the panels
|
||||
@@ -533,14 +516,7 @@ fn drag_released(app: &mut VectorcraftApp, ui: &Ui, f: &Pointing, d: Drag, p: Po
|
||||
let _ = app.session.cancel_interaction();
|
||||
dispatch(
|
||||
app,
|
||||
&PointerEvent {
|
||||
kind: PointerKind::Up,
|
||||
pos: Point::ZERO,
|
||||
mods: Mods::default(),
|
||||
backquote: ui.input(|i| i.key_down(egui::Key::Backtick)),
|
||||
pressure: 1.0,
|
||||
pen: None,
|
||||
},
|
||||
&PointerEvent { kind: PointerKind::Up, pos: Point::ZERO, mods: Mods::default(), backquote, pressure: 1.0, pen: None },
|
||||
view,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3051,6 +3051,67 @@ fn egui_backtick_drag_uses_key_state_at_release() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The release takes Backtick as it was at the button-up, when the key goes up or down later in the
|
||||
/// same frame.
|
||||
#[test]
|
||||
fn backtick_changed_after_the_button_up_in_its_frame_does_not_change_the_release() {
|
||||
use egui::Event;
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
let tile = app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("object.pattern.make", json!({"ids": [tile], "name": "Dots", "width": 20, "height": 20})).unwrap();
|
||||
app.run("object.pattern.done", json!({})).unwrap();
|
||||
let id = app.run("shape.rectangle", json!({"x": 100, "y": 100, "width": 50, "height": 50})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("paint.setFill", json!({"ids": [id], "swatch": "Dots"})).unwrap();
|
||||
app.run("select.set", json!({"ids": [id]})).unwrap();
|
||||
app.select_tool("selection");
|
||||
app.ui.task_bar = false;
|
||||
app.ui.view.rulers = false;
|
||||
app.ui.view.smart_guides = false;
|
||||
app.ui.view.snap_to_point = false;
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
let xf = Xf::new(app.canvas_rect.unwrap(), app.view().unwrap());
|
||||
let from = xf.to_screen(Point::new(125.0, 125.0));
|
||||
let to = xf.to_screen(Point::new(145.0, 125.0));
|
||||
let button = |pos, pressed| Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
let key = |pressed| Event::Key { key: egui::Key::Backtick, physical_key: None, pressed, repeat: false, modifiers: Default::default() };
|
||||
let node = |app: &VectorcraftApp| app.session.doc().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().clone();
|
||||
let pattern_x = |node: &vectorcraft_doc::Node| {
|
||||
let vectorcraft_color::Paint::Pattern { xf, .. } = node.appearance.fill_paint() else { panic!("pattern fill expected") };
|
||||
xf.as_coeffs()[4]
|
||||
};
|
||||
let before = node(&app);
|
||||
frame(&mut app, &ctx, vec![Event::PointerMoved(from), button(from, true)]);
|
||||
frame(&mut app, &ctx, vec![key(true), Event::PointerMoved(to)]);
|
||||
frame(&mut app, &ctx, vec![button(to, false), key(false)]);
|
||||
let pattern_only = node(&app);
|
||||
assert!(app.session.active().unwrap().interaction.is_none(), "committed");
|
||||
assert_eq!(pattern_only.geometric_bounds(), before.geometric_bounds(), "Backtick held at the button-up keeps geometry still");
|
||||
assert!((pattern_x(&pattern_only) - pattern_x(&before) - 20.0).abs() < 0.001, "Backtick held at the button-up moves the pattern");
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(node(&app), before);
|
||||
frame(&mut app, &ctx, vec![Event::PointerMoved(from), button(from, true)]);
|
||||
frame(&mut app, &ctx, vec![Event::PointerMoved(to)]);
|
||||
frame(&mut app, &ctx, vec![button(to, false), key(true)]);
|
||||
let ordinary = node(&app);
|
||||
frame(&mut app, &ctx, vec![key(false)]);
|
||||
assert!(
|
||||
(ordinary.geometric_bounds().unwrap().x0 - before.geometric_bounds().unwrap().x0 - 20.0).abs() < 0.001,
|
||||
"Backtick pressed after the button-up commits an ordinary move"
|
||||
);
|
||||
assert!((pattern_x(&ordinary) - pattern_x(&before)).abs() < 0.001, "the ordinary move keeps the pattern matrix");
|
||||
// A focus loss after the button-up lets go of Backtick with no release event.
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(node(&app), before);
|
||||
frame(&mut app, &ctx, vec![Event::PointerMoved(from), button(from, true)]);
|
||||
frame(&mut app, &ctx, vec![key(true), Event::PointerMoved(to)]);
|
||||
frame(&mut app, &ctx, vec![button(to, false), Event::WindowFocused(false)]);
|
||||
let unfocused = node(&app);
|
||||
assert_eq!(unfocused.geometric_bounds(), before.geometric_bounds(), "a focus loss after the button-up keeps geometry still");
|
||||
assert!((pattern_x(&unfocused) - pattern_x(&before) - 20.0).abs() < 0.001, "a focus loss after the button-up moves the pattern");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mirror_axis_drag_uses_canvas_coordinates_in_a_zoomed_rotated_view() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
|
||||
Reference in New Issue
Block a user