Thanks @HiroKenEz.
This commit is contained in:
1 parent
6496ec86ec
commit
45ae1bef95
2 files changed
+90
-2
No files matched your search
@@ -522,8 +522,20 @@ fn value_box(ui: &mut egui::Ui, label: &str, value: &mut String, at: Pos2, selec
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How far (logical points) from an arrow's stem a press still takes the arrow.
|
||||||
|
const ARROW_STEM_REACH: f32 = 6.0;
|
||||||
|
|
||||||
|
/// The distance from `p` to the segment from `a` to `b` on screen.
|
||||||
|
fn to_segment(p: Pos2, a: Pos2, b: Pos2) -> f32 {
|
||||||
|
let v = b - a;
|
||||||
|
let t = if v.length_sq() > 0.0 { ((p - a).dot(v) / v.length_sq()).clamp(0.0, 1.0) } else { 0.0 };
|
||||||
|
p.distance(a + v * t)
|
||||||
|
}
|
||||||
|
|
||||||
/// A draggable arrow from `base` along `n` showing `l` (at least a short stub); dragging writes
|
/// A draggable arrow from `base` along `n` showing `l` (at least a short stub); dragging writes
|
||||||
/// the snapped length into `value`. Returns the tip's screen position.
|
/// the snapped length into `value`. The tip and the stem take the drag (#1740): pressed on the
|
||||||
|
/// stem, the arrow keeps its length until the pointer moves, and then moves with it as far as the
|
||||||
|
/// pointer does. Returns the tip's screen position.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn drag_arrow(
|
fn drag_arrow(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
@@ -545,11 +557,28 @@ fn drag_arrow(
|
|||||||
proj.to_screen(base + n * shown)
|
proj.to_screen(base + n * shown)
|
||||||
};
|
};
|
||||||
let mut ts = tip(l)?;
|
let mut ts = tip(l)?;
|
||||||
let r = ui.interact(egui::Rect::from_center_size(ts, vec2(20.0, 20.0)), id, egui::Sense::drag());
|
let at_tip = egui::Rect::from_center_size(ts, vec2(20.0, 20.0));
|
||||||
|
// Over the stem, the arrow answers where the pointer is.
|
||||||
|
let on_stem = ui.input(|i| i.pointer.hover_pos()).filter(|p| !at_tip.contains(*p) && to_segment(*p, bs, ts) <= ARROW_STEM_REACH);
|
||||||
|
let area = on_stem.map_or(at_tip, |p| egui::Rect::from_center_size(p, vec2(2.0 * ARROW_STEM_REACH, 2.0 * ARROW_STEM_REACH)));
|
||||||
|
let r = ui.interact(area, id, egui::Sense::drag());
|
||||||
|
// How far along the arrow the press was short of its end: the drag keeps that gap.
|
||||||
|
let grab = id.with("stem grab");
|
||||||
|
if r.drag_started() {
|
||||||
|
let gap = on_stem.zip(r.interact_pointer_pos()).and_then(|(_, p)| along(proj, p, base, n)).map(|v| v - l);
|
||||||
|
ui.data_mut(|d| {
|
||||||
|
if let Some(g) = gap.filter(|g| g.is_finite()) {
|
||||||
|
d.insert_temp(grab, g);
|
||||||
|
} else {
|
||||||
|
d.remove::<f64>(grab);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
if r.dragged()
|
if r.dragged()
|
||||||
&& let Some(p) = r.interact_pointer_pos()
|
&& let Some(p) = r.interact_pointer_pos()
|
||||||
&& let Some(v) = along(proj, p, base, n)
|
&& let Some(v) = along(proj, p, base, n)
|
||||||
{
|
{
|
||||||
|
let v = v - ui.data(|d| d.get_temp::<f64>(grab)).unwrap_or(0.0);
|
||||||
let free = ui.input(|i| i.modifiers.alt || i.modifiers.ctrl || i.modifiers.command);
|
let free = ui.input(|i| i.modifiers.alt || i.modifiers.ctrl || i.modifiers.command);
|
||||||
let step = snap_step(half_height);
|
let step = snap_step(half_height);
|
||||||
let mut v = if free { v } else { (v / step).round() * step };
|
let mut v = if free { v } else { (v / step).round() * step };
|
||||||
|
|||||||
@@ -394,3 +394,62 @@ fn extrusion_arrow_tracks_the_current_drag_frame() {
|
|||||||
assert!((drawn[0] - expected[0]).hypot(drawn[1] - expected[1]) < 0.1, "arrow {drawn:?} trails current value {expected:?}");
|
assert!((drawn[0] - expected[0]).hypot(drawn[1] - expected[1]) < 0.1, "arrow {drawn:?} trails current value {expected:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The world point `at` mm along the open extrude's arrow.
|
||||||
|
fn on_arrow(h: &Harness, at: f64) -> Value {
|
||||||
|
let d = h.app.dialog.as_ref().unwrap();
|
||||||
|
let (base, normal) = crate::canvas::anchor(&h.app, d).unwrap();
|
||||||
|
let p = base + normal.unwrap() * at;
|
||||||
|
json!({"world":[p.x, p.y, p.z]})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dialog_distance(h: &Harness) -> f64 {
|
||||||
|
let d = h.app.dialog.as_ref().unwrap();
|
||||||
|
let crate::dialogs::Kind::Extrude { distance, .. } = &d.kind else { panic!("extrude") };
|
||||||
|
h.app.session.doc.eval(distance, solidcraft_engine::doc::expr::Kind::Length).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The arrow's stem takes the drag as its tip does (#1740): pressed 4 mm along a 10 mm arrow and
|
||||||
|
/// moved to 9 mm, it grows by the 5 mm the pointer moved, not to where the pointer is.
|
||||||
|
#[test]
|
||||||
|
fn the_extrude_arrow_drags_from_its_stem() {
|
||||||
|
let mut h = make(json!({"distance":10}));
|
||||||
|
open(&mut h);
|
||||||
|
let (from, to) = (on_arrow(&h, 4.0), on_arrow(&h, 9.0));
|
||||||
|
h.step(&json!({"drag":[from, to]})).unwrap();
|
||||||
|
assert!((dialog_distance(&h) - 15.0).abs() < 1e-9, "{}", dialog_distance(&h));
|
||||||
|
h.step(&json!({"widget":"OK"})).unwrap();
|
||||||
|
let bounds = h.app.session.world_state().body("Main").unwrap().mesh().bounds();
|
||||||
|
assert!((bounds.max.z - 15.0).abs() < 1e-7, "{bounds:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A click on the arrow's stem is the arrow's (#1740): a new extrude of a square sketched on a
|
||||||
|
/// block keeps its profile picked and its distance. Before, the click went through to the square
|
||||||
|
/// under the stem and unpicked it.
|
||||||
|
#[test]
|
||||||
|
fn a_click_on_the_extrude_arrow_stem_keeps_the_profile() {
|
||||||
|
let mut h = Harness::new();
|
||||||
|
h.app.home.open = false;
|
||||||
|
for (id, p) in [
|
||||||
|
("sketch.create", json!({"plane": "XY"})),
|
||||||
|
("sketch.rectangle.two_point", json!({"p0": [0, 0], "p1": [60, 20]})),
|
||||||
|
("sketch.finish", json!({})),
|
||||||
|
("solid.extrude", json!({"distance": 10})),
|
||||||
|
("sketch.create", json!({"plane": {"origin": [0, 0, 10], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}})),
|
||||||
|
("sketch.rectangle.two_point", json!({"p0": [5, 5], "p1": [15, 15]})),
|
||||||
|
] {
|
||||||
|
h.app.run(id, p).unwrap();
|
||||||
|
}
|
||||||
|
h.app.finish_sketch();
|
||||||
|
h.app.start("solid.extrude");
|
||||||
|
h.frames(4);
|
||||||
|
let picked = |h: &Harness| h.app.dialog.as_ref().unwrap().inputs.iter().map(|i| i.items.len()).collect::<Vec<_>>();
|
||||||
|
let before = picked(&h);
|
||||||
|
assert!(before.iter().any(|n| *n > 0), "{before:?}");
|
||||||
|
let distance = dialog_distance(&h);
|
||||||
|
let at = on_arrow(&h, distance * 0.3);
|
||||||
|
h.step(&json!({"click": at})).unwrap();
|
||||||
|
h.frames(2);
|
||||||
|
assert_eq!(picked(&h), before);
|
||||||
|
assert!((dialog_distance(&h) - distance).abs() < 1e-9, "{} for {distance}", dialog_distance(&h));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user