Thanks @DuBearte.
This commit is contained in:
1 parent
6422ef84d5
commit
33cd031701
3 files changed
+100
-3
No files matched your search
@@ -220,7 +220,9 @@ fn live(app: &SolveApp, t: &Tool, e: Vec2, px: f64) -> Vec<(f64, Vec2)> {
|
||||
}
|
||||
("sketch.arc.center_point", 2) => {
|
||||
let a0 = (a - first).angle();
|
||||
vec![(((e - first).angle() - a0).rem_euclid(std::f64::consts::TAU), e)]
|
||||
// Out along the radius, clear of the cursor: a box on it would take the hover and
|
||||
// the click meant for a point there (#2201).
|
||||
vec![(((e - first).angle() - a0).rem_euclid(std::f64::consts::TAU), e + dir_or_x(e - first) * (2.5 * off))]
|
||||
}
|
||||
("sketch.conic", 2) => {
|
||||
// `rho` is the curve's fullness, not a distance: the conic passes through
|
||||
@@ -234,7 +236,9 @@ fn live(app: &SolveApp, t: &Tool, e: Vec2, px: f64) -> Vec<(f64, Vec2)> {
|
||||
}
|
||||
("sketch.slot.center_to_center" | "sketch.slot.overall", 2) => {
|
||||
let dir = dir_or_x(a - first);
|
||||
vec![(2.0 * dir.cross(e - first).abs(), e)]
|
||||
let side = if dir.cross(e - first) < 0.0 { -1.0 } else { 1.0 };
|
||||
// Beyond the cursor, away from the centre line, as for the arc (#2201).
|
||||
vec![(2.0 * dir.cross(e - first).abs(), e + dir.perp() * (side * 2.5 * off))]
|
||||
}
|
||||
_ => Vec::new(),
|
||||
}
|
||||
@@ -402,6 +406,7 @@ fn boxes(app: &SolveApp, ui: &mut egui::Ui, proj: &Proj, t: &mut Tool) -> Option
|
||||
}
|
||||
let mut completed = false;
|
||||
let mut any_focused = false;
|
||||
let view = app.viewport.layout.as_ref().map(|l| l.drawable).filter(|r| r.is_finite() && r.is_positive());
|
||||
for (i, b) in t.dims.iter_mut().enumerate() {
|
||||
let Some((v, at)) = lv.get(i).copied() else { continue };
|
||||
if !b.locked {
|
||||
@@ -417,7 +422,7 @@ fn boxes(app: &SolveApp, ui: &mut egui::Ui, proj: &Proj, t: &mut Tool) -> Option
|
||||
s.cursor.set_char_range(Some(egui::text_selection::CCursorRange::two(egui::text::CCursor::new(0), egui::text::CCursor::new(n))));
|
||||
s.store(ui.ctx(), id);
|
||||
}
|
||||
let pos: Pos2 = sp + vec2(-34.0, -11.0);
|
||||
let pos = in_view(sp + vec2(-34.0, -11.0), view, ui.ctx().pointer_latest_pos());
|
||||
crate::frame::area(egui::Id::new(("sc_dim_area", i))).fixed_pos(pos).order(egui::Order::Foreground).show(ui.ctx(), |ui| {
|
||||
let fill = if b.locked { tk.accent_soft } else { tk.panel };
|
||||
egui::Frame::popup(ui.style()).fill(fill).inner_margin(egui::Margin::symmetric(3, 1)).show(ui, |ui| {
|
||||
@@ -465,6 +470,28 @@ fn boxes(app: &SolveApp, ui: &mut egui::Ui, proj: &Proj, t: &mut Tool) -> Option
|
||||
(enter && (any_focused || nothing)).then_some(e)
|
||||
}
|
||||
|
||||
/// About the size of a value box, for keeping it in view.
|
||||
const BOX: egui::Vec2 = egui::vec2(76.0, 24.0);
|
||||
|
||||
/// Where a value box whose top left would be `pos` goes so it stays in the drawing area `view`:
|
||||
/// as it is when it fits; else mirrored through the pointer (a box set off the cursor, past the
|
||||
/// view's edge, goes to the cursor's other side, still clear of it, #2201); else pushed inside.
|
||||
fn in_view(pos: Pos2, view: Option<egui::Rect>, pointer: Option<Pos2>) -> Pos2 {
|
||||
let Some(view) = view else { return pos };
|
||||
let fits = |p: Pos2| view.contains_rect(egui::Rect::from_min_size(p, BOX));
|
||||
if fits(pos) {
|
||||
return pos;
|
||||
}
|
||||
if let Some(c) = pointer {
|
||||
let mirrored = c + (c - (pos + BOX * 0.5)) - BOX * 0.5;
|
||||
if fits(mirrored) {
|
||||
return mirrored;
|
||||
}
|
||||
}
|
||||
let max = (view.max - BOX).max(view.min);
|
||||
pos.clamp(view.min, max)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "sketch_dims_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::{BOX, in_view};
|
||||
use crate::scenario::Harness;
|
||||
|
||||
fn drawing(command: &str) -> Harness {
|
||||
@@ -316,3 +317,25 @@ fn a_line_end_on_a_circle_outside_the_axis_snap_stays_at_the_cursor() {
|
||||
let end = sk.point(b).unwrap();
|
||||
assert!((end.len() - 30.0).abs() < 1e-9 && end.dist(at) < 0.5, "{end:?}");
|
||||
}
|
||||
|
||||
/// A value box set off the cursor stays in the drawing area (#2201): past an edge it goes to the
|
||||
/// cursor's other side, still clear of it; with no room either side it is pushed inside.
|
||||
#[test]
|
||||
fn value_boxes_stay_in_view_and_off_the_cursor() {
|
||||
let view = egui::Rect::from_min_max(egui::pos2(250.0, 130.0), egui::pos2(1650.0, 960.0));
|
||||
let cursor = egui::pos2(565.0, 142.0);
|
||||
// 50 px above the cursor: over the ribbon.
|
||||
let above = cursor + egui::vec2(-34.0, -11.0 - 50.0);
|
||||
let p = in_view(above, Some(view), Some(cursor));
|
||||
let rect = egui::Rect::from_min_size(p, BOX);
|
||||
assert!(view.contains_rect(rect), "{rect:?} in {view:?}");
|
||||
assert!(!rect.expand(2.0).contains(cursor), "{rect:?} clear of {cursor:?}");
|
||||
assert!(rect.min.y > cursor.y, "below the cursor now");
|
||||
// A box that fits stays where it is; without a view nothing moves.
|
||||
let inside = egui::pos2(700.0, 500.0);
|
||||
assert_eq!(in_view(inside, Some(view), Some(cursor)), inside);
|
||||
assert_eq!(in_view(above, None, Some(cursor)), above);
|
||||
// No room on either side: inside the view all the same.
|
||||
let tiny = egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(80.0, 30.0));
|
||||
assert!(tiny.contains_rect(egui::Rect::from_min_size(in_view(egui::pos2(-200.0, -200.0), Some(tiny), Some(egui::pos2(40.0, 15.0))), BOX)));
|
||||
}
|
||||
@@ -182,3 +182,50 @@ fn typed_drawing_preserves_construction_and_centerline_modes_at_undo_limit() {
|
||||
crate::sketch_palette::set_mode(&mut h.app, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// The third pick of a Center Point Arc reaches existing geometry under the pointer: the value
|
||||
/// box (the arc's sweep, a slot's width) stays clear of the cursor instead of sitting on it
|
||||
/// (#2201).
|
||||
#[test]
|
||||
fn the_arc_end_pick_snaps_to_a_point_under_the_value_box() {
|
||||
for (command, picks, end) in [
|
||||
("sketch.arc.center_point", [[0.0, 0.0], [10.0, 0.0]], [0.0, 10.0]),
|
||||
("sketch.slot.center_to_center", [[-10.0, 0.0], [10.0, 0.0]], [0.0, 10.0]),
|
||||
] {
|
||||
let mut h = Harness::new();
|
||||
h.app.run("sketch.create", json!({"plane":"XY"})).unwrap();
|
||||
h.app.run("sketch.line", json!({"points":[[0, 0], end], "infer": false})).unwrap();
|
||||
h.app.run("sketch.construction", json!({"curves":["l1"]})).unwrap();
|
||||
h.call("ui.view", json!({"view":"top"}));
|
||||
h.app.cam.distance = 60.0;
|
||||
h.app.cam.target = solidcraft_engine::geom::Vec3::new(0.0, 5.0, 0.0);
|
||||
h.settle();
|
||||
h.step(&json!({"start":command})).unwrap();
|
||||
for p in picks {
|
||||
h.step(&json!({"click":{"world":[p[0], p[1], 0]}})).unwrap();
|
||||
}
|
||||
h.step(&json!({"move":{"world":[end[0], end[1], 0]}})).unwrap();
|
||||
let cursor = h.at(&json!({"world":[end[0], end[1], 0]})).unwrap();
|
||||
let cursor = egui::pos2(cursor[0] as f32, cursor[1] as f32);
|
||||
let blocking = h.ctx.memory(|m| m.area_rect(egui::Id::new(("sc_dim_area", 0usize)))).filter(|r| r.expand(2.0).contains(cursor));
|
||||
assert!(blocking.is_none(), "{command}: the value box {blocking:?} sits under the cursor {cursor:?}");
|
||||
h.step(&json!({"click":{"world":[end[0], end[1], 0]}})).unwrap();
|
||||
// A slot's third pick sets its width only; the arc's end takes the point it lands on.
|
||||
if command != "sketch.arc.center_point" {
|
||||
continue;
|
||||
}
|
||||
let state = h.app.session.world_state();
|
||||
let sketch = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
|
||||
let line_end = sketch.curves.iter().find(|c| c.id == "l1").and_then(|c| match c.kind {
|
||||
CurveKind::Line { b, .. } => Some(b),
|
||||
_ => None,
|
||||
});
|
||||
let snapped =
|
||||
sketch.constraints.iter().any(|c| matches!(&c.kind, ConstraintKind::Coincident { p, q } if Some(*p) == line_end || Some(*q) == line_end))
|
||||
|| sketch.curves.iter().filter(|c| c.id != "l1").any(|c| match c.kind {
|
||||
CurveKind::Arc { a, b, .. } => Some(a) == line_end || Some(b) == line_end,
|
||||
_ => false,
|
||||
});
|
||||
assert!(snapped, "{command}: the third pick snapped to the line's end: {:?}", sketch.constraints.iter().map(|c| &c.kind).collect::<Vec<_>>());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user