mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 21:27:00 -06:00
Canvas: Shift, Alt and Cmd/Ctrl pressed or released mid-drag apply with the pointer held still (#4711)
This commit is contained in:
@@ -78,6 +78,16 @@ pub(super) fn tool_drag_moved(ui: &Ui, xf: &Xf, p: Pos2, pointer: &egui::Pointer
|
||||
true
|
||||
}
|
||||
|
||||
/// Whether the tool should get another drag event at a pointer held still because Shift, Alt,
|
||||
/// Cmd/Ctrl or Space went down or up (#4711): a scale switches to proportional or from the centre
|
||||
/// at once. Records `m` as the modifiers last seen; only once the press has dragged.
|
||||
pub(super) fn tool_drag_mods_changed(ui: &Ui, m: Mods) -> bool {
|
||||
let id = egui::Id::unique("canvas-tool-drag-mods");
|
||||
let (dragged, last) = ui.data(|d| (d.get_temp::<Point>(tool_drag_doc_id()).is_some(), d.get_temp::<Mods>(id)));
|
||||
ui.data_mut(|d| d.insert_temp(id, m));
|
||||
dragged && last.is_some_and(|l| l != m)
|
||||
}
|
||||
|
||||
/// 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,7 @@
|
||||
use super::artboards::{artboard_name_at, begin_artboard_name_edit, name_press_id, select_named_artboard};
|
||||
use super::drag::{
|
||||
HoverContext, apply_requests, dispatch, drag_art_out, drag_id, drag_layer_id, drag_pos_id, drop_press, mods, on_canvas, pen_pressure,
|
||||
tool_drag_doc_id, tool_drag_moved, tool_frame_moves, tool_holds, tool_press_doc_id,
|
||||
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,
|
||||
@@ -361,7 +361,8 @@ fn drag_held(app: &mut VectorcraftApp, ui: &Ui, f: &Pointing, d: Drag, p: Pos2)
|
||||
// the last position only, so a costly drag preview runs once a frame.
|
||||
let moves = tool_frame_moves(ui, app);
|
||||
// A frame whose pointer ends where the last one did still moved in between.
|
||||
if tool_drag_moved(ui, &xf, p, pointer) || !moves.is_empty() {
|
||||
let mods_changed = tool_drag_mods_changed(ui, mods(m, space));
|
||||
if tool_drag_moved(ui, &xf, p, pointer) || mods_changed || !moves.is_empty() {
|
||||
for (at, force) in moves {
|
||||
let pressure = force.unwrap_or_else(|| pen_pressure(ui, app, false));
|
||||
dispatch(app, &PointerEvent { kind: PointerKind::Drag, pos: xf.to_doc(at), mods: mods(m, space), pressure }, view);
|
||||
|
||||
@@ -6054,6 +6054,43 @@ fn tool_drag_moved_without_pointer_delta() {
|
||||
assert!(moved, "a new document position runs even when egui reports no delta");
|
||||
}
|
||||
|
||||
/// #4711: Shift pressed and released while a corner handle is held still scales proportionally and
|
||||
/// then freely again at once, without waiting for the pointer to move.
|
||||
#[test]
|
||||
fn a_modifier_changed_mid_scale_applies_with_the_pointer_held_still() {
|
||||
use egui::{Event, Modifiers};
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||||
app.ui.view.smart_guides = false;
|
||||
app.ui.view.snap_to_point = false;
|
||||
let id = app.session.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 50})).unwrap()["id"].as_u64().unwrap();
|
||||
app.select_tool("selection");
|
||||
let ctx = egui::Context::default();
|
||||
frame(&mut app, &ctx, vec![]);
|
||||
let xf = Xf::new(app.canvas_rect.unwrap(), app.view().unwrap());
|
||||
let with = |app: &mut VectorcraftApp, mut events: Vec<Event>, modifiers: Modifiers| {
|
||||
events.insert(0, Event::ModifiersChanged(modifiers));
|
||||
let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), events, ..Default::default() };
|
||||
let mut out = ctx.run_ui(raw, |ui| show(app, ui));
|
||||
out.textures_delta.clear();
|
||||
};
|
||||
let button = |pos, pressed| Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed, modifiers: Modifiers::NONE };
|
||||
let height =
|
||||
|app: &VectorcraftApp| app.session.active().unwrap().doc.node(vectorcraft_doc::NodeId(id)).unwrap().geometric_bounds().unwrap().height();
|
||||
let corner = xf.to_screen(Point::new(200.0, 150.0));
|
||||
let to = xf.to_screen(Point::new(300.0, 150.0));
|
||||
with(&mut app, vec![Event::PointerMoved(corner)], Modifiers::NONE);
|
||||
with(&mut app, vec![button(corner, true)], Modifiers::NONE);
|
||||
with(&mut app, vec![Event::PointerMoved(to)], Modifiers::NONE);
|
||||
assert!((height(&app) - 50.0).abs() < 1e-6, "a free scale keeps the height: {}", height(&app));
|
||||
with(&mut app, vec![], Modifiers::SHIFT);
|
||||
assert!((height(&app) - 100.0).abs() < 1e-6, "Shift scales proportionally at once: {}", height(&app));
|
||||
with(&mut app, vec![], Modifiers::NONE);
|
||||
assert!((height(&app) - 50.0).abs() < 1e-6, "releasing Shift frees it at once: {}", height(&app));
|
||||
with(&mut app, vec![button(to, false)], Modifiers::NONE);
|
||||
assert!(app.session.active().unwrap().interaction.is_none(), "committed");
|
||||
}
|
||||
|
||||
/// #834 review: a press held still is not a drag. Before the press has dragged, the first held
|
||||
/// frame without a delta at the press's own position sends nothing, so a click stays a click;
|
||||
/// the first move after it does.
|
||||
|
||||
Reference in New Issue
Block a user