mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-14 14:26:45 -04:00
Co-authored-by: Brandon Thomas <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2 files changed
+130
No files matched your search
@@ -1231,6 +1231,38 @@ pub fn show(app: &mut CadApp, ui: &mut egui::Ui) {
|
||||
hatch_dialog = true;
|
||||
}
|
||||
|
||||
// While a command asks for a point, every click places one, however fast the drafting (#815).
|
||||
// egui drops a click whose button comes up a few pixels from where it went down (a drag to it),
|
||||
// and eframe hands a slow frame's clicks over together, which egui reports as one.
|
||||
let point_prompt = app.session.current_prompt().is_some_and(|p| cadcraft_engine::pointmod::accepts(&p))
|
||||
&& app.session.pending_window.is_none()
|
||||
&& !app.session.realtime_zoom_active();
|
||||
let pressed_primary = pressed_primary || (point_prompt && resp.drag_stopped_by(egui::PointerButton::Primary));
|
||||
if point_prompt && inside && pressed_primary && !middle_down && app.cmd.options.is_none() && app.cmd.buffer.is_empty() {
|
||||
let releases: Vec<egui::Pos2> = ui.input(|i| {
|
||||
i.events
|
||||
.iter()
|
||||
.filter_map(|e| match e {
|
||||
egui::Event::PointerButton { pos, button: egui::PointerButton::Primary, pressed: false, .. } => Some(*pos),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
// The last one is placed below, at the cursor, like any click.
|
||||
if let Some((_, earlier)) = releases.split_last() {
|
||||
for pos in earlier.iter().filter(|p| rect.contains(**p)) {
|
||||
let w = effective_point(app, xf.to_world(*pos), &xf, ui.ctx());
|
||||
let result = match app.canvas.snap {
|
||||
Some(hit) => app.session.input_snapped(hit),
|
||||
None => app.session.input(Input::Point(w)),
|
||||
};
|
||||
if let Err(e) = result {
|
||||
app.session.echo(e.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let raw_world = hover_pos.filter(|_| inside).map(|p| xf.to_world(p));
|
||||
let dyn_frame = crate::cmdline::dyn_frame(app);
|
||||
if let Some(w) = raw_world {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
//! Fast drafting: every click on the canvas places a point, however fast it comes (#815).
|
||||
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_engine::doc::{Drawing, EntityKind, Space};
|
||||
use cadcraft_engine::geom::Vec2;
|
||||
use cadcraft_ui_egui::{CadApp, Services};
|
||||
use egui::{Event, Modifiers, PointerButton, Pos2, vec2};
|
||||
use egui_kittest::Harness;
|
||||
use serde_json::json;
|
||||
|
||||
fn harness() -> Harness<'static, CadApp> {
|
||||
let mut session = Session::empty();
|
||||
session.open_drawing(Drawing::new_imperial(), "Rapid clicks", None);
|
||||
session.execute("view.set", &json!({"center":[50,50],"height":100})).unwrap();
|
||||
session.settings.osmode = 0;
|
||||
session.settings.snapmode = false;
|
||||
session.settings.orthomode = false;
|
||||
let mut app = CadApp::new(session, Services::default());
|
||||
app.canvas.zoom_pending = false;
|
||||
let mut h = Harness::builder().with_size(vec2(1600.0, 1000.0)).build_ui_state(
|
||||
|ui, app: &mut CadApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(5);
|
||||
h
|
||||
}
|
||||
|
||||
fn screen(h: &Harness<'_, CadApp>, p: Vec2) -> Pos2 {
|
||||
h.state().canvas.xf.unwrap().to_screen(p)
|
||||
}
|
||||
|
||||
fn button(h: &mut Harness<'_, CadApp>, pos: Pos2, pressed: bool) {
|
||||
h.event(Event::PointerMoved(pos));
|
||||
h.event(Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
|
||||
}
|
||||
|
||||
fn start_line_at(h: &mut Harness<'_, CadApp>, first: Vec2) {
|
||||
h.state_mut().start("line");
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().session.running.as_ref().unwrap().id, "line");
|
||||
let pos = screen(h, first);
|
||||
button(h, pos, true);
|
||||
h.run_steps(1);
|
||||
button(h, pos, false);
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
fn lines(h: &Harness<'_, CadApp>) -> Vec<(Vec2, Vec2)> {
|
||||
let st = h.state().session.state().unwrap();
|
||||
st.doc
|
||||
.space(&Space::Model)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|e| match &e.kind {
|
||||
EntityKind::Line(l) => Some((l.a.xy(), l.b.xy())),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_clicks_within_one_frame_place_two_points() {
|
||||
let mut h = harness();
|
||||
start_line_at(&mut h, Vec2::new(10.0, 10.0));
|
||||
let (a, b) = (screen(&h, Vec2::new(40.0, 10.0)), screen(&h, Vec2::new(40.0, 40.0)));
|
||||
// A slow frame: both clicks arrive before the next one is drawn, and eframe hands them over
|
||||
// together. (`Harness::event` would run a frame per event, so they go into the input directly.)
|
||||
for pos in [a, b] {
|
||||
for pressed in [true, false] {
|
||||
h.input_mut().events.push(Event::PointerMoved(pos));
|
||||
h.input_mut().events.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
|
||||
}
|
||||
}
|
||||
h.run_steps(2);
|
||||
let got = lines(&h);
|
||||
assert_eq!(got.len(), 2, "{got:?}");
|
||||
assert!(got[0].1.near(Vec2::new(40.0, 10.0), 1e-4), "{got:?}");
|
||||
assert!(got[1].1.near(Vec2::new(40.0, 40.0), 1e-4), "{got:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_click_made_while_the_mouse_moves_still_places_the_point() {
|
||||
let mut h = harness();
|
||||
start_line_at(&mut h, Vec2::new(10.0, 10.0));
|
||||
let down = screen(&h, Vec2::new(40.0, 10.0));
|
||||
button(&mut h, down, true);
|
||||
h.run_steps(1);
|
||||
// The hand is already on its way to the next point when the button comes up.
|
||||
let up = down + vec2(12.0, 0.0);
|
||||
button(&mut h, up, false);
|
||||
h.run_steps(2);
|
||||
let got = lines(&h);
|
||||
assert_eq!(got.len(), 1, "{got:?}");
|
||||
assert!(got[0].1.near(h.state().canvas.xf.unwrap().to_world(up), 1e-4), "{got:?}");
|
||||
}
|
||||
Reference in new issue
Block a user