mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 21:57:04 +00:00
3D (Classic): the panel's and a dialog's track cubes each keep their own focus and drag
With the 3D and Materials panel and a classic 3D dialog both showing a track cube, the two cubes shared one widget id, so the keyboard focus of one moved the other, and a drag on one cube was read by the other: Escape there put the first cube's settings into it, and its button-up check ended the gesture before the cube being dragged saw the release. Each cube now has its own id, keyed by its dialog or panel, and only the cube a drag started on follows, restores or ends it.
This commit is contained in:
@@ -61,6 +61,11 @@ impl Harness {
|
||||
self.ctx.read_response(Id::unique(id)).unwrap().rect
|
||||
}
|
||||
|
||||
/// The open dialog's track cube.
|
||||
fn cube_id(&self) -> Id {
|
||||
super::super::three_d_classic::cube_id(&self.app.ui.dialog.as_ref().unwrap().kind)
|
||||
}
|
||||
|
||||
fn click(&mut self, p: Pos2) {
|
||||
self.frame(vec![Event::PointerMoved(p), button(p, true)]);
|
||||
self.frame(vec![button(p, false)]);
|
||||
@@ -484,7 +489,7 @@ fn the_track_cube_turns_the_classic_effects_live_in_one_undo_step() {
|
||||
h.settle();
|
||||
h.rect("Off-Axis Front");
|
||||
let start = ["rotationX", "rotationY", "rotationZ"].map(|k| h.value(k));
|
||||
let cube = h.response("three-d-track-cube");
|
||||
let cube = h.ctx.read_response(h.cube_id()).unwrap().rect;
|
||||
// A face, dragged right: the object turns, the document previews it, and the preset is Custom.
|
||||
h.drag(cube.center(), vec2(30.0, 0.0));
|
||||
let turned = ["rotationX", "rotationY", "rotationZ"].map(|k| h.value(k));
|
||||
@@ -513,7 +518,7 @@ fn the_track_cube_turns_the_classic_effects_live_in_one_undo_step() {
|
||||
assert!(h.value("rotationZ") > turned[2], "clockwise: {} -> {}", turned[2], h.value("rotationZ"));
|
||||
// With the keyboard focus (the drags gave it), Right turns about the screen's vertical axis.
|
||||
let z = h.value("rotationZ");
|
||||
assert!(h.ctx.memory(|m| m.has_focus(Id::unique("three-d-track-cube"))), "the drag gave the cube the keyboard focus");
|
||||
assert!(h.ctx.memory(|m| m.has_focus(h.cube_id())), "the drag gave the cube the keyboard focus");
|
||||
h.frame(vec![Event::Key { key: egui::Key::PageUp, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }]);
|
||||
h.settle();
|
||||
assert!((h.value("rotationZ") - (z - 1.0)).abs() < 0.011, "Page Up turns 1° counterclockwise");
|
||||
@@ -535,11 +540,11 @@ fn the_track_cube_is_off_while_a_rotation_is_mixed() {
|
||||
let mut h = Harness::with("M100 100 L200 100 L200 200 L100 200 Z", CLASSIC_ROTATE, vec2(1400.0, 1000.0));
|
||||
h.app.ui.dialog.as_mut().unwrap().fields.insert("rotationX".into(), Value::Null);
|
||||
h.settle();
|
||||
let cube = h.response("three-d-track-cube");
|
||||
let cube = h.ctx.read_response(h.cube_id()).unwrap().rect;
|
||||
h.drag(cube.center(), vec2(30.0, 0.0));
|
||||
let d = h.app.ui.dialog.as_ref().unwrap();
|
||||
assert_eq!(d.fields.get("rotationX"), Some(&Value::Null));
|
||||
assert!(!h.ctx.read_response(Id::unique("three-d-track-cube")).unwrap().sense.senses_drag());
|
||||
assert!(!h.ctx.read_response(h.cube_id()).unwrap().sense.senses_drag());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -49,7 +49,8 @@ fn angles_stay_in_the_fields_range_to_a_hundredth() {
|
||||
}
|
||||
|
||||
fn drag(grip: Grip, angles: [f64; 3], from: Pos2, to: Pos2, shift: bool) -> [f64; 3] {
|
||||
let mut g = CubeDrag { grip, matrix: rotation(angles), origin: from, last: from, lock: None, fields: Default::default(), pass: 0 };
|
||||
let mut g =
|
||||
CubeDrag { grip, matrix: rotation(angles), origin: from, last: from, lock: None, fields: Default::default(), pass: 0, owner: Id::NULL };
|
||||
// In five steps, as a pointer moves.
|
||||
let mut near = angles;
|
||||
for k in 1..=5 {
|
||||
@@ -124,6 +125,7 @@ fn a_drag_ends_when_the_cube_is_no_longer_drawn() {
|
||||
lock: None,
|
||||
fields: Default::default(),
|
||||
pass,
|
||||
owner: Id::NULL,
|
||||
};
|
||||
ui.data_mut(|m| m.insert_temp(cube_drag_id(), drag));
|
||||
assert!(cube_dragging(ui.ctx()));
|
||||
@@ -135,6 +137,36 @@ fn a_drag_ends_when_the_cube_is_no_longer_drawn() {
|
||||
assert!(!cube_dragging(&ctx), "two passes without the cube end the gesture");
|
||||
}
|
||||
|
||||
/// With the 3D and Materials panel and a 3D dialog both showing a cube, a gesture on one cube is
|
||||
/// left alone by the other: Escape there does not put the first cube's settings into it.
|
||||
#[test]
|
||||
fn a_cube_leaves_a_gesture_on_the_other_cube_alone() {
|
||||
let ctx = egui::Context::default();
|
||||
let mut d = Dialog::new("effect", json!({ "rotationX": 10.0, "rotationY": 0.0, "rotationZ": 0.0 }));
|
||||
let escape = egui::Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::NONE };
|
||||
let input = egui::RawInput { events: vec![escape], ..Default::default() };
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
let pass = ui.ctx().cumulative_pass_nr();
|
||||
let other = CubeDrag {
|
||||
grip: Grip::Face,
|
||||
matrix: rotation([50.0, 0.0, 0.0]),
|
||||
origin: Pos2::ZERO,
|
||||
last: Pos2::ZERO,
|
||||
lock: None,
|
||||
fields: json!({ "rotationX": 50.0, "rotationY": 0.0, "rotationZ": 0.0 }).as_object().cloned().unwrap_or_default(),
|
||||
pass,
|
||||
owner: cube_id(crate::panels::three_d::ID),
|
||||
};
|
||||
assert_ne!(cube_id(crate::panels::three_d::ID), cube_id(&d.kind), "each cube has its own focus");
|
||||
ui.data_mut(|m| m.insert_temp(cube_drag_id(), other));
|
||||
track_cube(ui, &mut d);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
assert_eq!(d.f64("rotationX", 0.0), 10.0, "the dialog keeps its own rotation");
|
||||
// The button is up in that pass, and the other cube, drawn later, still has the release to handle.
|
||||
assert!(ctx.data(|m| m.get_temp::<CubeDrag>(cube_drag_id())).is_some(), "the other cube's gesture is still there");
|
||||
}
|
||||
|
||||
/// The centre is on the front face, the ring's band on the ring, and an edge's middle on that edge.
|
||||
#[test]
|
||||
fn grips_follow_what_is_under_the_pointer() {
|
||||
|
||||
@@ -277,6 +277,9 @@ struct CubeDrag {
|
||||
fields: serde_json::Map<String, Value>,
|
||||
/// The last pass that drew the cube during the gesture.
|
||||
pass: u64,
|
||||
/// The cube the gesture started on. The 3D and Materials panel and a 3D dialog can each show
|
||||
/// a cube at the same time, and only this one follows the gesture.
|
||||
owner: Id,
|
||||
}
|
||||
|
||||
impl CubeDrag {
|
||||
@@ -287,6 +290,12 @@ impl CubeDrag {
|
||||
}
|
||||
}
|
||||
|
||||
/// The track cube of the dialog or panel of kind `kind`. The 3D and Materials panel and a 3D
|
||||
/// dialog can show a cube at the same time, each with its own keyboard focus.
|
||||
pub(super) fn cube_id(kind: &str) -> Id {
|
||||
Id::unique(("three-d-track-cube", kind))
|
||||
}
|
||||
|
||||
fn cube_drag_id() -> Id {
|
||||
Id::unique("three-d-track-cube-drag")
|
||||
}
|
||||
@@ -359,7 +368,7 @@ pub(super) fn track_cube(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
label(ui, name);
|
||||
let (rect, _) = ui.allocate_exact_size(Vec2::splat(CUBE_BOX), Sense::hover());
|
||||
let enabled = ui.is_enabled() && !mixed;
|
||||
let id = Id::unique("three-d-track-cube");
|
||||
let id = cube_id(&d.kind);
|
||||
let resp = ui.interact(rect, id, if enabled { Sense::click_and_drag() } else { Sense::hover() });
|
||||
let center = rect.center();
|
||||
let value = format!("X {:.2}°, Y {:.2}°, Z {:.2}°", now[0], now[1], now[2]);
|
||||
@@ -375,14 +384,24 @@ pub(super) fn track_cube(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
ui.ctx().memory_mut(|m| m.stop_text_input());
|
||||
let matrix = rotation(now);
|
||||
let pass = ui.ctx().cumulative_pass_nr();
|
||||
let drag = CubeDrag { grip: grip_at(matrix, center, origin), matrix, origin, last: origin, lock: None, fields: d.fields.clone(), pass };
|
||||
let drag = CubeDrag {
|
||||
grip: grip_at(matrix, center, origin),
|
||||
matrix,
|
||||
origin,
|
||||
last: origin,
|
||||
lock: None,
|
||||
fields: d.fields.clone(),
|
||||
pass,
|
||||
owner: id,
|
||||
};
|
||||
ui.data_mut(|m| m.insert_temp(cube_drag_id(), drag));
|
||||
}
|
||||
if enabled && (resp.drag_started() || resp.clicked()) {
|
||||
resp.request_focus();
|
||||
}
|
||||
let pass = ui.ctx().cumulative_pass_nr();
|
||||
let mut drag = ui.data(|m| m.get_temp::<CubeDrag>(cube_drag_id()));
|
||||
// A gesture on the other cube (the panel's or the dialog's) is left to that cube.
|
||||
let mut drag = ui.data(|m| m.get_temp::<CubeDrag>(cube_drag_id())).filter(|g| g.owner == id);
|
||||
if drag.as_ref().is_some_and(|g| !g.live(pass)) {
|
||||
ui.data_mut(|m| m.remove::<CubeDrag>(cube_drag_id()));
|
||||
drag = None;
|
||||
@@ -404,7 +423,8 @@ pub(super) fn track_cube(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
ui.data_mut(|m| m.insert_temp(cube_drag_id(), current.clone()));
|
||||
}
|
||||
}
|
||||
if resp.drag_stopped() || !ui.input(|i| i.pointer.primary_down()) {
|
||||
// Only this cube's gesture ends here; the other cube may still have the release to handle.
|
||||
if drag.is_some() && (resp.drag_stopped() || !ui.input(|i| i.pointer.primary_down())) {
|
||||
ui.data_mut(|m| m.remove::<CubeDrag>(cube_drag_id()));
|
||||
}
|
||||
if enabled && resp.has_focus() {
|
||||
|
||||
Reference in New Issue
Block a user