forked from 1b-agent/opencadstudio-upstream
refactor(selection): split 31-field SelectionState into five sub-structs
SelectionState now holds view, gesture, input, menu and orbit sub-structs; all 450 access sites updated mechanically with no behavior change. Deletes three write-only dead fields (box_last, box_last_crossing, poly_last_crossing). Hand-rolled clears in commands and dim_viewport route through the new clear methods; bench fixture updated. Full suite: 1976 passed / 0 failed / 25 ignored.
This commit is contained in:
1 parent
8c2cfa2c8f
commit
7aafd9c710
27 files changed
+512
-469
No files matched your search
@@ -50,7 +50,9 @@ use OpenCADStudio::scene::parametric_constraints::{
|
||||
};
|
||||
use OpenCADStudio::scene::pick::hit_test::{box_hit, click_hit};
|
||||
use OpenCADStudio::scene::pick::interaction_index::InteractionIndex;
|
||||
use OpenCADStudio::scene::pick::selection_state::SelectionState;
|
||||
use OpenCADStudio::scene::pick::selection_state::{
|
||||
SelectionGesture, SelectionState, SelectionView,
|
||||
};
|
||||
use OpenCADStudio::scene::pipeline::wire_arena::partition_wires;
|
||||
use OpenCADStudio::scene::view::camera::Camera;
|
||||
use OpenCADStudio::scene::{ChangeKind, Scene};
|
||||
@@ -713,12 +715,17 @@ fn bench_selection_cloning(runner: &mut BenchmarkRunner) {
|
||||
}
|
||||
|
||||
let mut state = SelectionState {
|
||||
vp_size: (1920.0, 1080.0),
|
||||
poly_points: vec![Point::new(10.0, 10.0); 64],
|
||||
view: SelectionView {
|
||||
vp_size: (1920.0, 1080.0),
|
||||
},
|
||||
gesture: SelectionGesture {
|
||||
box_anchor: Some(Point::new(0.0, 0.0)),
|
||||
box_current: Some(Point::new(100.0, 100.0)),
|
||||
poly_points: vec![Point::new(10.0, 10.0); 64],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
state.box_anchor = Some(Point::new(0.0, 0.0));
|
||||
state.box_current = Some(Point::new(100.0, 100.0));
|
||||
|
||||
let n = if runner.quick_mode { 1_000 } else { 5_000 };
|
||||
let runs = 5;
|
||||
@@ -793,6 +800,7 @@ fn bench_ui_ribbon_view_construction(runner: &mut BenchmarkRunner) {
|
||||
visible: true,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
vp_frozen: None,
|
||||
},
|
||||
LayerInfo {
|
||||
name: "DRAWING".to_string(),
|
||||
@@ -800,6 +808,7 @@ fn bench_ui_ribbon_view_construction(runner: &mut BenchmarkRunner) {
|
||||
visible: true,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
vp_frozen: None,
|
||||
},
|
||||
LayerInfo {
|
||||
name: "DIMENSIONS".to_string(),
|
||||
@@ -807,6 +816,7 @@ fn bench_ui_ribbon_view_construction(runner: &mut BenchmarkRunner) {
|
||||
visible: true,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
vp_frozen: None,
|
||||
},
|
||||
LayerInfo {
|
||||
name: "ANNOTATIONS".to_string(),
|
||||
@@ -814,6 +824,7 @@ fn bench_ui_ribbon_view_construction(runner: &mut BenchmarkRunner) {
|
||||
visible: true,
|
||||
frozen: false,
|
||||
locked: false,
|
||||
vp_frozen: None,
|
||||
},
|
||||
],
|
||||
"0",
|
||||
@@ -1371,7 +1382,7 @@ fn bench_ui_constraint_glyphs(runner: &mut BenchmarkRunner) {
|
||||
);
|
||||
scene.note_parametric_constraint_applied(ParametricScope::ModelSpace, id, 3);
|
||||
}
|
||||
scene.selection.borrow_mut().vp_size = (1920.0, 1080.0);
|
||||
scene.selection.borrow_mut().view.vp_size = (1920.0, 1080.0);
|
||||
let vp = (1920.0_f32, 1080.0_f32);
|
||||
|
||||
// Prime the cache so every timed call below is a hit (same key, same Arc).
|
||||
@@ -1526,6 +1537,8 @@ fn build_selection_overlay_element(
|
||||
snap_angle_deg: 0.0,
|
||||
point_mode: false,
|
||||
pick_pending: false,
|
||||
hide_arms: false,
|
||||
snap_color: None,
|
||||
},
|
||||
SelectionVisualOptions::default(),
|
||||
empty_glyphs,
|
||||
|
||||
@@ -1757,7 +1757,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(text["added"], 1, "{text}");
|
||||
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (1600.0, 800.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (1600.0, 800.0);
|
||||
app.tabs[i].scene.fit_all();
|
||||
let (_, max) = app.tabs[i]
|
||||
.scene
|
||||
@@ -2775,7 +2775,7 @@ mod tests {
|
||||
app.pick_add = add;
|
||||
app.pick_drag_rect = rect;
|
||||
let _ = app.run_command_line("LINE 0,0 10,10");
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let _ = app.run_command_line("ZOOM EXTENTS");
|
||||
// Both directions. Crossing = a right → left diagonal sweep (the
|
||||
// freeform path may be degenerate — crossing counts hits).
|
||||
@@ -2819,16 +2819,16 @@ mod tests {
|
||||
}
|
||||
{
|
||||
let sel = app.tabs[i].scene.selection.borrow();
|
||||
assert!(sel.left_dragging, "drag must start (add={add} rect={rect})");
|
||||
assert!(sel.input.left_dragging, "drag must start (add={add} rect={rect})");
|
||||
if rect {
|
||||
// Rectangle mode drives the box machinery, not the lasso.
|
||||
assert!(
|
||||
sel.box_anchor.is_some() && sel.box_current.is_some() && !sel.poly_active,
|
||||
sel.gesture.box_anchor.is_some() && sel.gesture.box_current.is_some() && !sel.gesture.poly_active,
|
||||
"rect marquee must arm the box (add={add})"
|
||||
);
|
||||
} else {
|
||||
assert!(sel.poly_active, "lasso must start (add={add})");
|
||||
assert!(sel.poly_points.len() >= 3, "lasso points (add={add})");
|
||||
assert!(sel.gesture.poly_active, "lasso must start (add={add})");
|
||||
assert!(sel.gesture.poly_points.len() >= 3, "lasso points (add={add})");
|
||||
}
|
||||
}
|
||||
let _ = app.update(Message::ViewportLeftRelease);
|
||||
@@ -3157,7 +3157,7 @@ mod tests {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
{
|
||||
app.tabs[0].scene.selection.borrow_mut().vp_size = (1920.0, 1080.0);
|
||||
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (1920.0, 1080.0);
|
||||
app.tabs[0].scene.sync_tiles_from_panes(1920.0, 1080.0);
|
||||
}
|
||||
|
||||
@@ -3226,7 +3226,7 @@ mod tests {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
{
|
||||
app.tabs[0].scene.selection.borrow_mut().vp_size = (1920.0, 1080.0);
|
||||
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (1920.0, 1080.0);
|
||||
app.tabs[0].scene.sync_tiles_from_panes(1920.0, 1080.0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1148,7 +1148,7 @@ impl OpenCADStudio {
|
||||
// The command picked the path; the hit test lives here, where
|
||||
// the camera and the drawing's geometry are. Everything after
|
||||
// matches what a lasso does, Remove included.
|
||||
let canvas = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let canvas = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
|
||||
@@ -364,7 +364,7 @@ impl OpenCADStudio {
|
||||
.is_some_and(|command| command.wants_point_pick_context())
|
||||
{
|
||||
let scene = &self.tabs[i].scene;
|
||||
let size = scene.selection.borrow().vp_size;
|
||||
let size = scene.selection.borrow().view.vp_size;
|
||||
let edit = scene.viewport_edit_frame(size);
|
||||
let tile = edit
|
||||
.as_ref()
|
||||
@@ -919,8 +919,8 @@ impl OpenCADStudio {
|
||||
let crossing = matches!(kw.as_str(), "C" | "CROSSING");
|
||||
{
|
||||
let mut selection = self.tabs[i].scene.selection.borrow_mut();
|
||||
selection.box_crossing = crossing;
|
||||
selection.box_crossing_locked = true;
|
||||
selection.gesture.box_crossing = crossing;
|
||||
selection.gesture.box_crossing_locked = true;
|
||||
}
|
||||
let hint = if crossing {
|
||||
crate::t!("Crossing: specify first corner.")
|
||||
|
||||
+45
-56
@@ -80,7 +80,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.right_click_entered = false;
|
||||
.input.right_click_entered = false;
|
||||
// A fresh command starts at the polar/cartesian default — clear
|
||||
// any `,`-driven reshape and locked dynamic-input values from a
|
||||
// previous command. Otherwise a bare Enter on the first point prompt
|
||||
@@ -184,18 +184,7 @@ impl OpenCADStudio {
|
||||
{
|
||||
let mut selection = self.tabs[i].scene.selection.borrow_mut();
|
||||
// Cancel the active selection gesture before the new command starts.
|
||||
selection.left_down = false;
|
||||
selection.left_press_pos = None;
|
||||
selection.left_press_time = None;
|
||||
selection.left_dragging = false;
|
||||
selection.box_anchor = None;
|
||||
selection.box_anchor_world = None;
|
||||
selection.box_current = None;
|
||||
selection.box_crossing = false;
|
||||
selection.box_crossing_locked = false;
|
||||
selection.poly_active = false;
|
||||
selection.poly_points.clear();
|
||||
selection.poly_crossing = false;
|
||||
selection.clear_left_selection_gesture();
|
||||
}
|
||||
// Cancel any running command before starting a new one.
|
||||
if self.tabs[i].active_cmd.is_some() {
|
||||
@@ -961,31 +950,31 @@ mod marquee_cancel_tests {
|
||||
fn arm_marquee(app: &mut OpenCADStudio) {
|
||||
let i = app.active_tab;
|
||||
let mut sel = app.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = true;
|
||||
sel.left_press_pos = Some(iced::Point::new(10.0, 10.0));
|
||||
sel.left_press_time = Some(Instant::now());
|
||||
sel.left_dragging = true;
|
||||
sel.box_anchor = Some(iced::Point::new(10.0, 10.0));
|
||||
sel.box_anchor_world = Some(glam::DVec3::new(1.0, 2.0, 0.0));
|
||||
sel.box_current = Some(iced::Point::new(40.0, 40.0));
|
||||
sel.box_crossing = true;
|
||||
sel.box_crossing_locked = true;
|
||||
sel.input.left_down = true;
|
||||
sel.input.left_press_pos = Some(iced::Point::new(10.0, 10.0));
|
||||
sel.input.left_press_time = Some(Instant::now());
|
||||
sel.input.left_dragging = true;
|
||||
sel.gesture.box_anchor = Some(iced::Point::new(10.0, 10.0));
|
||||
sel.gesture.box_anchor_world = Some(glam::DVec3::new(1.0, 2.0, 0.0));
|
||||
sel.gesture.box_current = Some(iced::Point::new(40.0, 40.0));
|
||||
sel.gesture.box_crossing = true;
|
||||
sel.gesture.box_crossing_locked = true;
|
||||
}
|
||||
|
||||
/// Arm a held lasso drag.
|
||||
fn arm_lasso(app: &mut OpenCADStudio) {
|
||||
let i = app.active_tab;
|
||||
let mut sel = app.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = true;
|
||||
sel.left_press_pos = Some(iced::Point::new(10.0, 10.0));
|
||||
sel.left_press_time = Some(Instant::now());
|
||||
sel.left_dragging = true;
|
||||
sel.poly_active = true;
|
||||
sel.poly_points = vec![
|
||||
sel.input.left_down = true;
|
||||
sel.input.left_press_pos = Some(iced::Point::new(10.0, 10.0));
|
||||
sel.input.left_press_time = Some(Instant::now());
|
||||
sel.input.left_dragging = true;
|
||||
sel.gesture.poly_active = true;
|
||||
sel.gesture.poly_points = vec![
|
||||
iced::Point::new(10.0, 10.0),
|
||||
iced::Point::new(20.0, 30.0),
|
||||
];
|
||||
sel.poly_crossing = true;
|
||||
sel.gesture.poly_crossing = true;
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -997,15 +986,15 @@ mod marquee_cancel_tests {
|
||||
let _ = app.dispatch_command("LINE");
|
||||
|
||||
let sel = app.tabs[i].scene.selection.borrow();
|
||||
assert!(!sel.left_down);
|
||||
assert!(sel.left_press_pos.is_none());
|
||||
assert!(sel.left_press_time.is_none());
|
||||
assert!(!sel.left_dragging);
|
||||
assert!(sel.box_anchor.is_none());
|
||||
assert!(sel.box_anchor_world.is_none());
|
||||
assert!(sel.box_current.is_none());
|
||||
assert!(!sel.box_crossing);
|
||||
assert!(!sel.box_crossing_locked);
|
||||
assert!(!sel.input.left_down);
|
||||
assert!(sel.input.left_press_pos.is_none());
|
||||
assert!(sel.input.left_press_time.is_none());
|
||||
assert!(!sel.input.left_dragging);
|
||||
assert!(sel.gesture.box_anchor.is_none());
|
||||
assert!(sel.gesture.box_anchor_world.is_none());
|
||||
assert!(sel.gesture.box_current.is_none());
|
||||
assert!(!sel.gesture.box_crossing);
|
||||
assert!(!sel.gesture.box_crossing_locked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1017,13 +1006,13 @@ mod marquee_cancel_tests {
|
||||
let _ = app.dispatch_command("LINE");
|
||||
|
||||
let sel = app.tabs[i].scene.selection.borrow();
|
||||
assert!(!sel.left_down);
|
||||
assert!(sel.left_press_pos.is_none());
|
||||
assert!(sel.left_press_time.is_none());
|
||||
assert!(!sel.left_dragging);
|
||||
assert!(!sel.poly_active);
|
||||
assert!(sel.poly_points.is_empty());
|
||||
assert!(!sel.poly_crossing);
|
||||
assert!(!sel.input.left_down);
|
||||
assert!(sel.input.left_press_pos.is_none());
|
||||
assert!(sel.input.left_press_time.is_none());
|
||||
assert!(!sel.input.left_dragging);
|
||||
assert!(!sel.gesture.poly_active);
|
||||
assert!(sel.gesture.poly_points.is_empty());
|
||||
assert!(!sel.gesture.poly_crossing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1039,8 +1028,8 @@ mod marquee_cancel_tests {
|
||||
Some("LINE")
|
||||
);
|
||||
let sel = app.tabs[i].scene.selection.borrow();
|
||||
assert!(sel.box_anchor.is_none());
|
||||
assert!(sel.box_anchor_world.is_none());
|
||||
assert!(sel.gesture.box_anchor.is_none());
|
||||
assert!(sel.gesture.box_anchor_world.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1052,15 +1041,15 @@ mod marquee_cancel_tests {
|
||||
let _ = app.dispatch_command("ORTHO");
|
||||
|
||||
let sel = app.tabs[i].scene.selection.borrow();
|
||||
assert!(sel.left_down);
|
||||
assert!(sel.left_press_pos.is_some());
|
||||
assert!(sel.left_press_time.is_some());
|
||||
assert!(sel.left_dragging);
|
||||
assert!(sel.box_anchor.is_some());
|
||||
assert!(sel.box_anchor_world.is_some());
|
||||
assert!(sel.box_current.is_some());
|
||||
assert!(sel.box_crossing);
|
||||
assert!(sel.box_crossing_locked);
|
||||
assert!(sel.input.left_down);
|
||||
assert!(sel.input.left_press_pos.is_some());
|
||||
assert!(sel.input.left_press_time.is_some());
|
||||
assert!(sel.input.left_dragging);
|
||||
assert!(sel.gesture.box_anchor.is_some());
|
||||
assert!(sel.gesture.box_anchor_world.is_some());
|
||||
assert!(sel.gesture.box_current.is_some());
|
||||
assert!(sel.gesture.box_crossing);
|
||||
assert!(sel.gesture.box_crossing_locked);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1496,7 +1496,7 @@ impl OpenCADStudio {
|
||||
.filter(|v| v.is_finite())
|
||||
.ok_or_else(|| failure("invalid_point", "Missing finite y"))?
|
||||
as f32;
|
||||
let size = self.tabs[self.active_tab].scene.selection.borrow().vp_size;
|
||||
let size = self.tabs[self.active_tab].scene.selection.borrow().view.vp_size;
|
||||
if x < 0. || y < 0. || x > size.0 || y > size.1 {
|
||||
return Err(failure(
|
||||
"outside_viewport",
|
||||
|
||||
@@ -439,7 +439,7 @@ impl OpenCADStudio {
|
||||
"layout":tab.scene.current_layout,
|
||||
"ucs":tab.active_ucs.as_ref().map(|u|json!({"name":u.name,"origin":[u.origin.x,u.origin.y,u.origin.z],"x_axis":[u.x_axis.x,u.x_axis.y,u.x_axis.z],"y_axis":[u.y_axis.x,u.y_axis.y,u.y_axis.z],"elevation":u.elevation})),
|
||||
"cursor":{"world":tab.last_cursor_world.to_array(),"screen":[tab.last_cursor_screen.x,tab.last_cursor_screen.y]},
|
||||
"viewport_size":({let s=tab.scene.selection.borrow();[s.vp_size.0,s.vp_size.1]}),
|
||||
"viewport_size":({let s=tab.scene.selection.borrow();[s.view.vp_size.0,s.view.vp_size.1]}),
|
||||
"camera":({let c=tab.scene.camera.borrow();json!({"target":c.target.to_array(),"rotation":[c.rotation.x,c.rotation.y,c.rotation.z,c.rotation.w],"distance":c.distance,"fov_y":c.fov_y,"projection":format!("{:?}",c.projection),"yaw":c.yaw,"pitch":c.pitch})}),
|
||||
"mtext_editor":self.mtext_editor.as_ref().map(|e|json!({"text":e.content.text(),"height":e.height,"style":e.style})),
|
||||
"text_editor":self.text_inline.is_some(),"event_cursor":self.control.serial,
|
||||
@@ -1358,7 +1358,7 @@ impl OpenCADStudio {
|
||||
.ok_or("Renderer returned malformed image data")?;
|
||||
let mut actual_scope = "window";
|
||||
|
||||
let (vp_w, vp_h) = self.tabs[self.active_tab].scene.selection.borrow().vp_size;
|
||||
let (vp_w, vp_h) = self.tabs[self.active_tab].scene.selection.borrow().view.vp_size;
|
||||
let mut vp_logical_width = if vp_w > 0.0 { vp_w } else { image.width() as f32 / s.scale_factor };
|
||||
let mut vp_logical_height = if vp_h > 0.0 { vp_h } else { image.height() as f32 / s.scale_factor };
|
||||
|
||||
@@ -2138,7 +2138,7 @@ mod tests {
|
||||
let i = app.active_tab;
|
||||
{
|
||||
let tab = &mut app.tabs[i];
|
||||
tab.scene.selection.borrow_mut().vp_size = (1920.0, 1080.0);
|
||||
tab.scene.selection.borrow_mut().view.vp_size = (1920.0, 1080.0);
|
||||
tab.scene.sync_tiles_from_panes(1920.0, 1080.0);
|
||||
tab.scene.fit_all(); // bring the line into the pane so snaps can hit
|
||||
}
|
||||
@@ -2200,7 +2200,7 @@ mod tests {
|
||||
let i = app.active_tab;
|
||||
{
|
||||
let tab = &mut app.tabs[i];
|
||||
tab.scene.selection.borrow_mut().vp_size = (1920.0, 1080.0);
|
||||
tab.scene.selection.borrow_mut().view.vp_size = (1920.0, 1080.0);
|
||||
tab.scene.sync_tiles_from_panes(1920.0, 1080.0);
|
||||
tab.scene.fit_all();
|
||||
}
|
||||
|
||||
@@ -604,7 +604,7 @@ mod tests {
|
||||
app.automation_op(r#"{"op":"run","cmd":"LINE -10,-10 10,10"}"#);
|
||||
|
||||
// Set test canvas viewport size
|
||||
app.tabs[0].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
app.tabs[0].scene.fit_all();
|
||||
|
||||
let grounding = compute_grounding(&app, 800.0, 600.0, 800, 600);
|
||||
|
||||
@@ -20,10 +20,7 @@ pub(crate) enum DimensionMeasureSpace {
|
||||
impl OpenCADStudio {
|
||||
pub(crate) fn finish_command_click(&mut self, i: usize) {
|
||||
let mut selection = self.tabs[i].scene.selection.borrow_mut();
|
||||
selection.left_down = false;
|
||||
selection.left_press_pos = None;
|
||||
selection.left_press_time = None;
|
||||
selection.left_dragging = false;
|
||||
selection.input.clear_left_buttons();
|
||||
}
|
||||
|
||||
pub(crate) fn dimension_measure_space(&self, i: usize) -> DimensionMeasureSpace {
|
||||
|
||||
@@ -721,7 +721,7 @@ impl super::OpenCADStudio {
|
||||
return;
|
||||
}
|
||||
let mut state = MTextEditorState::new(pos, initial, height, handle);
|
||||
if let Some(p) = self.tabs[self.active_tab].scene.selection.borrow().last_move_pos {
|
||||
if let Some(p) = self.tabs[self.active_tab].scene.selection.borrow().input.last_move_pos {
|
||||
state.screen_anchor = p;
|
||||
}
|
||||
// Seed attachment / line-spacing / box width from the entity being edited.
|
||||
|
||||
@@ -99,7 +99,7 @@ impl OpenCADStudio {
|
||||
// after it takes keyboard focus. Gate input separately from its metadata.
|
||||
let allowed = self.spacemouse_focused && self.active_modal.is_none();
|
||||
let camera = scene.navigation_camera();
|
||||
let (w, h) = scene.selection.borrow().vp_size;
|
||||
let (w, h) = scene.selection.borrow().view.vp_size;
|
||||
let bounds = scene.active_model_tile_bounds(w, h);
|
||||
let aspect = scene
|
||||
.active_viewport
|
||||
@@ -211,17 +211,17 @@ impl OpenCADStudio {
|
||||
.acknowledge(&view.target, &self.navigation_target());
|
||||
self.spacemouse_pivot = (view.pivot_visible && moving)
|
||||
.then_some((self.navigation_target(), view.pivot));
|
||||
let (w, h) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (w, h) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
self.reproject_box_anchor(i, w, h);
|
||||
// The pointer may be stationary while the left hand navigates.
|
||||
// Refresh the active command's snap/preview against the new
|
||||
// camera, using the existing pointer path without a click.
|
||||
let cursor = {
|
||||
let selection = self.tabs[i].scene.selection.borrow();
|
||||
(!selection.left_down
|
||||
&& !selection.middle_down
|
||||
(!selection.input.left_down
|
||||
&& !selection.input.middle_down
|
||||
&& self.tabs[i].active_cmd.is_some())
|
||||
.then_some(selection.last_move_pos)
|
||||
.then_some(selection.input.last_move_pos)
|
||||
.flatten()
|
||||
};
|
||||
if let Some(cursor) = cursor {
|
||||
@@ -246,7 +246,7 @@ impl OpenCADStudio {
|
||||
return None;
|
||||
}
|
||||
let scene = &self.tabs[self.active_tab].scene;
|
||||
let (w, h) = scene.selection.borrow().vp_size;
|
||||
let (w, h) = scene.selection.borrow().view.vp_size;
|
||||
let rect = scene
|
||||
.active_viewport
|
||||
.and_then(|handle| scene.viewport_screen_rect(handle, (w, h)))
|
||||
@@ -367,7 +367,7 @@ mod tests {
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.vp_size = (800., 600.);
|
||||
.view.vp_size = (800., 600.);
|
||||
app.spacemouse_focused = true;
|
||||
app.spacemouse.test_connect();
|
||||
app.sync_spacemouse();
|
||||
|
||||
@@ -2065,7 +2065,7 @@ impl OpenCADStudio {
|
||||
let doc = &scene.document;
|
||||
let header = &doc.header;
|
||||
let camera = scene.camera.borrow();
|
||||
let (viewport_width, viewport_height) = scene.selection.borrow().vp_size;
|
||||
let (viewport_width, viewport_height) = scene.selection.borrow().view.vp_size;
|
||||
let aspect = if viewport_height > 0.0 {
|
||||
viewport_width as f64 / viewport_height as f64
|
||||
} else {
|
||||
|
||||
@@ -214,7 +214,7 @@ impl super::OpenCADStudio {
|
||||
creation,
|
||||
screen_anchor: iced::Point::new(60.0, 90.0),
|
||||
};
|
||||
if let Some(p) = self.tabs[self.active_tab].scene.selection.borrow().last_move_pos {
|
||||
if let Some(p) = self.tabs[self.active_tab].scene.selection.borrow().input.last_move_pos {
|
||||
state.screen_anchor = p;
|
||||
}
|
||||
self.text_inline = Some(state);
|
||||
|
||||
+14
-14
@@ -1177,13 +1177,13 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i].zoom_dynamic_mode = false;
|
||||
{
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
sel.orbit_pivot = None;
|
||||
sel.box_anchor = None;
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.box_crossing_locked = false;
|
||||
sel.input.middle_down = false;
|
||||
sel.input.middle_last_pos = None;
|
||||
sel.orbit.pivot = None;
|
||||
sel.gesture.box_anchor = None;
|
||||
sel.gesture.box_anchor_world = None;
|
||||
sel.gesture.box_current = None;
|
||||
sel.gesture.box_crossing_locked = false;
|
||||
}
|
||||
if was_pan {
|
||||
self.command_line.push_output(crate::t!("PAN ended.").as_ref());
|
||||
@@ -1230,8 +1230,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
{
|
||||
let mut sel = self.tabs[i_e].scene.selection.borrow_mut();
|
||||
if sel.context_menu.is_some() {
|
||||
sel.context_menu = None;
|
||||
if sel.menu.open_at.is_some() {
|
||||
sel.menu.open_at = None;
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
@@ -1261,13 +1261,13 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
self.tabs[i].scene.deselect_all();
|
||||
self.refresh_properties();
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.box_anchor = None;
|
||||
sel.gesture.box_anchor = None;
|
||||
// Also drop the world-space anchor, or the next pan/zoom
|
||||
// re-projects it back into `box_anchor` and the cancelled
|
||||
// marquee springs back to life (reproject_box_anchor).
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.box_crossing = false;
|
||||
sel.gesture.box_anchor_world = None;
|
||||
sel.gesture.box_current = None;
|
||||
sel.gesture.box_crossing = false;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -2086,7 +2086,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
|
||||
pub(super) fn on_qselect_open(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.open_at = None;
|
||||
let remembered = self.qselect_settings.clone();
|
||||
let scope = remembered.as_ref().map_or(
|
||||
crate::app::QSelectScope::CurrentSpace,
|
||||
|
||||
@@ -19,7 +19,7 @@ impl OpenCADStudio {
|
||||
pub(in crate::app) fn context_menu_open(&self) -> bool {
|
||||
self.tabs
|
||||
.get(self.active_tab)
|
||||
.is_some_and(|tab| tab.scene.selection.borrow().context_menu.is_some())
|
||||
.is_some_and(|tab| tab.scene.selection.borrow().menu.open_at.is_some())
|
||||
}
|
||||
|
||||
/// Everything the menu builder needs, read from the active tab. Called by
|
||||
@@ -71,7 +71,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow()
|
||||
.context_menu_ui
|
||||
.menu.ui
|
||||
.open_submenu;
|
||||
build_context_menu(&self.context_menu_context(), open)
|
||||
}
|
||||
@@ -80,10 +80,10 @@ impl OpenCADStudio {
|
||||
pub(in crate::app) fn close_context_menu(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
if sel.context_menu.is_none() {
|
||||
if sel.menu.open_at.is_none() {
|
||||
return Task::none();
|
||||
}
|
||||
sel.close_context_menu();
|
||||
sel.menu.close();
|
||||
drop(sel);
|
||||
self.focus_cmd_input()
|
||||
}
|
||||
@@ -91,7 +91,7 @@ impl OpenCADStudio {
|
||||
pub(in crate::app) fn on_context_menu_submenu_toggle(&mut self, id: SubmenuId) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
let ui = &mut sel.context_menu_ui;
|
||||
let ui = &mut sel.menu.ui;
|
||||
ui.open_submenu = if ui.open_submenu == Some(id) { None } else { Some(id) };
|
||||
drop(sel);
|
||||
// Keep the keyboard highlight on the header that was toggled.
|
||||
@@ -101,8 +101,8 @@ impl OpenCADStudio {
|
||||
.iter()
|
||||
.position(|s| s.header == Some(id));
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
if sel.context_menu_ui.highlighted.is_some() {
|
||||
sel.context_menu_ui.highlighted = header;
|
||||
if sel.menu.ui.highlighted.is_some() {
|
||||
sel.menu.ui.highlighted = header;
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -118,10 +118,10 @@ impl OpenCADStudio {
|
||||
let i = self.active_tab;
|
||||
{
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.close_context_menu();
|
||||
sel.menu.close();
|
||||
// A pick counts as "another interaction" for the Enter-first
|
||||
// right-click cycle: the next right-click acts as Enter again.
|
||||
sel.right_click_entered = false;
|
||||
sel.input.right_click_entered = false;
|
||||
}
|
||||
let focus = self.focus_cmd_input();
|
||||
let task = match action {
|
||||
@@ -174,7 +174,7 @@ impl OpenCADStudio {
|
||||
if rows.is_empty() {
|
||||
return Task::none();
|
||||
}
|
||||
let current = self.tabs[i].scene.selection.borrow().context_menu_ui.highlighted;
|
||||
let current = self.tabs[i].scene.selection.borrow().menu.ui.highlighted;
|
||||
let next_enabled = |from: usize, step: isize| -> usize {
|
||||
let n = rows.len() as isize;
|
||||
let mut idx = from as isize;
|
||||
@@ -193,7 +193,7 @@ impl OpenCADStudio {
|
||||
None => menu.default_index(),
|
||||
Some(cur) => next_enabled(cur, 1),
|
||||
};
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu_ui.highlighted = Some(idx);
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.ui.highlighted = Some(idx);
|
||||
Task::none()
|
||||
}
|
||||
ContextMenuNav::Up => {
|
||||
@@ -201,7 +201,7 @@ impl OpenCADStudio {
|
||||
None => menu.default_index(),
|
||||
Some(cur) => next_enabled(cur, -1),
|
||||
};
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu_ui.highlighted = Some(idx);
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.ui.highlighted = Some(idx);
|
||||
Task::none()
|
||||
}
|
||||
ContextMenuNav::Enter => {
|
||||
@@ -220,7 +220,7 @@ impl OpenCADStudio {
|
||||
if unique {
|
||||
self.on_context_menu_pick(rows[idx].action.clone())
|
||||
} else {
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu_ui.highlighted =
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.ui.highlighted =
|
||||
Some(idx);
|
||||
Task::none()
|
||||
}
|
||||
@@ -244,7 +244,7 @@ impl OpenCADStudio {
|
||||
ArrowKey::Left | ArrowKey::Right => {
|
||||
// On a submenu header, Right expands and Left collapses.
|
||||
let i = self.active_tab;
|
||||
let ui = self.tabs[i].scene.selection.borrow().context_menu_ui.clone();
|
||||
let ui = self.tabs[i].scene.selection.borrow().menu.ui.clone();
|
||||
let menu = self.current_context_menu();
|
||||
let rows = menu.selectable();
|
||||
let header = ui.highlighted.and_then(|c| rows.get(c)).and_then(|r| r.header);
|
||||
@@ -410,7 +410,7 @@ mod tests {
|
||||
fn app() -> OpenCADStudio {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
app.tabs[0].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let _ = app.update(Message::ViewportMove(Point::new(200.0, 150.0)));
|
||||
app
|
||||
}
|
||||
@@ -428,7 +428,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn menu_open(app: &OpenCADStudio) -> bool {
|
||||
app.tabs[0].scene.selection.borrow().context_menu.is_some()
|
||||
app.tabs[0].scene.selection.borrow().menu.open_at.is_some()
|
||||
}
|
||||
|
||||
fn active(app: &OpenCADStudio) -> Option<&'static str> {
|
||||
@@ -491,7 +491,7 @@ mod tests {
|
||||
let _ = app.update(Message::ViewportRightPress);
|
||||
{
|
||||
let mut sel = app.tabs[0].scene.selection.borrow_mut();
|
||||
sel.right_press_time = iced::time::Instant::now()
|
||||
sel.input.right_press_time = iced::time::Instant::now()
|
||||
.checked_sub(std::time::Duration::from_millis(400));
|
||||
}
|
||||
let _ = app.update(Message::ViewportRightRelease);
|
||||
@@ -558,7 +558,7 @@ mod tests {
|
||||
let _ = app.update(Message::ContextMenuPick(MenuAction::Command("LINE".into())));
|
||||
assert!(!menu_open(&app));
|
||||
assert_eq!(active(&app), Some("LINE"));
|
||||
assert!(!app.tabs[0].scene.selection.borrow().right_click_entered);
|
||||
assert!(!app.tabs[0].scene.selection.borrow().input.right_click_entered);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ mod tests {
|
||||
right_click(&mut app);
|
||||
let _ = app.update(Message::ContextMenuNavigate(ContextMenuNav::Down)); // Enter row
|
||||
assert_eq!(
|
||||
app.tabs[0].scene.selection.borrow().context_menu_ui.highlighted,
|
||||
app.tabs[0].scene.selection.borrow().menu.ui.highlighted,
|
||||
Some(0)
|
||||
);
|
||||
let _ = app.update(Message::ContextMenuNavigate(ContextMenuNav::Down)); // Cancel
|
||||
@@ -853,7 +853,7 @@ mod transparent_tests {
|
||||
fn line_app() -> OpenCADStudio {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
app.tabs[0].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let _ = app.dispatch_command("LINE");
|
||||
let _ = app.feed_command(StepInput::Point(DVec3::ZERO));
|
||||
app
|
||||
|
||||
@@ -640,7 +640,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
// produced a one-time jump plus a stuck drag. Column moves
|
||||
// arrive via Message::XrefColMove only.
|
||||
if self.dock_dragging.is_some() {
|
||||
let avail = self.tabs[self.active_tab].scene.selection.borrow().vp_size.1;
|
||||
let avail = self.tabs[self.active_tab].scene.selection.borrow().view.vp_size.1;
|
||||
let side = if point.x < self.win_size.0 * 0.5 {
|
||||
DockSide::Left
|
||||
} else {
|
||||
@@ -1671,7 +1671,7 @@ mod tests {
|
||||
app.show_block_palette = false;
|
||||
app.show_properties = true;
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (1600.0, 900.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (1600.0, 900.0);
|
||||
app.win_size = (1600.0, 900.0).into();
|
||||
let id = crate::ui::dock::PanelId::Properties;
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::DockGrab(id));
|
||||
@@ -1698,7 +1698,7 @@ mod tests {
|
||||
app.show_block_palette = true;
|
||||
app.show_properties = true;
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (1600.0, 900.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (1600.0, 900.0);
|
||||
app.win_size = (1600.0, 900.0).into();
|
||||
let id = crate::ui::dock::PanelId::Properties;
|
||||
let _ = app.on_dock(crate::ui::dock::DockMsg::DockGrab(id));
|
||||
|
||||
@@ -2014,7 +2014,7 @@ impl OpenCADStudio {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let interaction_task = {
|
||||
let wires = self.tabs[i].scene.hit_test_wires();
|
||||
let screen_height = self.tabs[i].scene.selection.borrow().vp_size.1;
|
||||
let screen_height = self.tabs[i].scene.selection.borrow().view.vp_size.1;
|
||||
self.prepare_interaction_index_task(i, wires, screen_height)
|
||||
.unwrap_or_else(Task::none)
|
||||
};
|
||||
@@ -5574,7 +5574,7 @@ impl OpenCADStudio {
|
||||
/// canvas outside the sheet, matching Model-space plotting.
|
||||
fn display_plot_window(&self) -> Option<(f64, f64, f64, f64)> {
|
||||
let scene = &self.tabs[self.active_tab].scene;
|
||||
let (canvas_w, canvas_h) = scene.selection.borrow().vp_size;
|
||||
let (canvas_w, canvas_h) = scene.selection.borrow().view.vp_size;
|
||||
let viewport = if scene.current_layout == "Model" {
|
||||
scene.active_model_tile_bounds(canvas_w, canvas_h)
|
||||
} else {
|
||||
|
||||
+36
-36
@@ -963,7 +963,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.context_menu = None;
|
||||
.menu.open_at = None;
|
||||
// Same guard as typed MTP/M2P: point step, not entity pick.
|
||||
let i = self.active_tab;
|
||||
let allowed = self.tabs[i].active_cmd.as_ref().is_some_and(|c| {
|
||||
@@ -2535,7 +2535,7 @@ impl OpenCADStudio {
|
||||
Message::Command(cmd) => {
|
||||
// Close viewport context menu if open.
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.open_at = None;
|
||||
// Any command also dismisses the Isolate action menu.
|
||||
self.isolate_popup_open = false;
|
||||
// "Pick window" (PLOTWINDOW) from Page Setup needs the backdrop
|
||||
@@ -3762,7 +3762,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.last_move_pos = Some(p);
|
||||
.input.last_move_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
self.focus_model_pane(idx);
|
||||
@@ -3828,45 +3828,45 @@ impl OpenCADStudio {
|
||||
// Shift+RMB: the one-shot snap override menu at the cursor —
|
||||
// pick a snap for just the next point, then it expires (#337).
|
||||
if self.shift_down {
|
||||
let pos = self.tabs[i].scene.selection.borrow().last_move_pos;
|
||||
let pos = self.tabs[i].scene.selection.borrow().input.last_move_pos;
|
||||
if let Some(p) = pos {
|
||||
self.snap_override_popup = Some(p);
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
let Some(p) = sel.last_move_pos else {
|
||||
let Some(p) = sel.input.last_move_pos else {
|
||||
return Task::none();
|
||||
};
|
||||
sel.context_menu = None;
|
||||
sel.right_down = true;
|
||||
sel.right_press_pos = Some(p);
|
||||
sel.right_press_time = Some(iced::time::Instant::now());
|
||||
sel.right_last_pos = Some(p);
|
||||
sel.right_dragging = false;
|
||||
sel.menu.open_at = None;
|
||||
sel.input.right_down = true;
|
||||
sel.input.right_press_pos = Some(p);
|
||||
sel.input.right_press_time = Some(iced::time::Instant::now());
|
||||
sel.input.right_last_pos = Some(p);
|
||||
sel.input.right_dragging = false;
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::ViewportRightRelease => {
|
||||
let i = self.active_tab;
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
let Some(click_pos) = sel.last_move_pos else {
|
||||
let Some(click_pos) = sel.input.last_move_pos else {
|
||||
return Task::none();
|
||||
};
|
||||
if !sel.right_down {
|
||||
if !sel.input.right_down {
|
||||
return Task::none();
|
||||
}
|
||||
let was_click = !sel.right_dragging;
|
||||
let was_click = !sel.input.right_dragging;
|
||||
// How long the button was held, for the time-sensitive mode.
|
||||
let held_ms = sel
|
||||
.right_press_time
|
||||
.input.right_press_time
|
||||
.map_or(0, |t| t.elapsed().as_millis() as i32);
|
||||
sel.right_down = false;
|
||||
sel.right_press_pos = None;
|
||||
sel.right_press_time = None;
|
||||
sel.right_last_pos = None;
|
||||
sel.right_dragging = false;
|
||||
sel.orbit_pivot = None;
|
||||
sel.input.right_down = false;
|
||||
sel.input.right_press_pos = None;
|
||||
sel.input.right_press_time = None;
|
||||
sel.input.right_last_pos = None;
|
||||
sel.input.right_dragging = false;
|
||||
sel.orbit.pivot = None;
|
||||
if !was_click {
|
||||
return Task::none();
|
||||
}
|
||||
@@ -3881,7 +3881,7 @@ impl OpenCADStudio {
|
||||
// line is empty. Pending text always runs and resets the Enter
|
||||
// cycle so the next right-click acts as Enter again.
|
||||
if !self.command_line.input.trim().is_empty() {
|
||||
sel.right_click_entered = false;
|
||||
sel.input.right_click_entered = false;
|
||||
drop(sel);
|
||||
return self.update(Message::CommandFinalize);
|
||||
}
|
||||
@@ -3904,19 +3904,19 @@ impl OpenCADStudio {
|
||||
held_ms >= self.right_click_hold_ms
|
||||
}
|
||||
super::settings::RightClickMode::EnterFirst => {
|
||||
!(has_cmd && !sel.right_click_entered)
|
||||
!(has_cmd && !sel.input.right_click_entered)
|
||||
}
|
||||
};
|
||||
if !open_menu {
|
||||
sel.right_click_entered = true;
|
||||
sel.input.right_click_entered = true;
|
||||
drop(sel);
|
||||
// CommandFinalize is Enter during a command and "repeat
|
||||
// the last command" when idle — exactly the quick
|
||||
// right-click.
|
||||
return self.update(Message::CommandFinalize);
|
||||
}
|
||||
sel.right_click_entered = false;
|
||||
sel.open_context_menu(click_pos);
|
||||
sel.input.right_click_entered = false;
|
||||
sel.menu.open(click_pos);
|
||||
drop(sel);
|
||||
// Take the keyboard away from the command-line field so keys
|
||||
// reach the menu through the global subscription; the field
|
||||
@@ -3929,11 +3929,11 @@ impl OpenCADStudio {
|
||||
Message::ViewportMiddleRelease => {
|
||||
let i = self.active_tab;
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
sel.input.middle_down = false;
|
||||
sel.input.middle_last_pos = None;
|
||||
// End of a Shift+MMB orbit — drop the captured pivot so the next
|
||||
// gesture recomputes it against the current selection. (#229)
|
||||
sel.orbit_pivot = None;
|
||||
sel.orbit.pivot = None;
|
||||
drop(sel);
|
||||
self.arm_hover_after_navigation(i);
|
||||
Task::none()
|
||||
@@ -4051,7 +4051,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow()
|
||||
.last_move_pos
|
||||
.input.last_move_pos
|
||||
.unwrap_or(self.cursor_pos);
|
||||
self.update_grip_hover(i, p);
|
||||
Task::none()
|
||||
@@ -4101,7 +4101,7 @@ impl OpenCADStudio {
|
||||
let (wires, screen_height) = if stale {
|
||||
(
|
||||
self.tabs[i].scene.hit_test_wires(),
|
||||
self.tabs[i].scene.selection.borrow().vp_size.1,
|
||||
self.tabs[i].scene.selection.borrow().view.vp_size.1,
|
||||
)
|
||||
} else {
|
||||
(queued_wires, screen_height)
|
||||
@@ -4114,7 +4114,7 @@ impl OpenCADStudio {
|
||||
if self.active_interaction_index.is_none() && !self.tabs.is_empty() {
|
||||
let i = self.active_tab.min(self.tabs.len() - 1);
|
||||
let wires = self.tabs[i].scene.hit_test_wires();
|
||||
let screen_height = self.tabs[i].scene.selection.borrow().vp_size.1;
|
||||
let screen_height = self.tabs[i].scene.selection.borrow().view.vp_size.1;
|
||||
self.prepare_interaction_index_task(i, wires, screen_height)
|
||||
.unwrap_or_else(Task::none)
|
||||
} else {
|
||||
@@ -5874,7 +5874,7 @@ impl OpenCADStudio {
|
||||
return Task::none();
|
||||
}
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.open_at = None;
|
||||
// A selected constraint-glyph pill takes Delete before entity
|
||||
// erase — the two selections are mutually exclusive (see
|
||||
// `Scene::selected_constraint`).
|
||||
@@ -6321,7 +6321,7 @@ impl OpenCADStudio {
|
||||
|
||||
Message::DrawOrderPickRef(above) => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.open_at = None;
|
||||
let to_move: Vec<_> = self.tabs[i].scene.selected.iter().cloned().collect();
|
||||
if to_move.is_empty() {
|
||||
self.command_line
|
||||
@@ -6337,7 +6337,7 @@ impl OpenCADStudio {
|
||||
|
||||
Message::SelectSimilar => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.open_at = None;
|
||||
let added = self.tabs[i].scene.select_similar();
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Select Similar: {} added.", added).as_ref());
|
||||
@@ -6347,7 +6347,7 @@ impl OpenCADStudio {
|
||||
|
||||
Message::InvertSelection => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.selection.borrow_mut().context_menu = None;
|
||||
self.tabs[i].scene.selection.borrow_mut().menu.open_at = None;
|
||||
let count = self.tabs[i].scene.invert_selection();
|
||||
self.command_line.push_output(
|
||||
crate::tf!("Invert Selection: {} object(s) selected.", count).as_ref(),
|
||||
|
||||
+210
-213
@@ -441,7 +441,7 @@ impl OpenCADStudio {
|
||||
.abs();
|
||||
if !self.point_size_relative && mag == 0.0 {
|
||||
let wpp = self.tabs[i].scene.world_per_pixel().unwrap_or(0.0);
|
||||
let viewport_height = self.tabs[i].scene.selection.borrow().vp_size.1;
|
||||
let viewport_height = self.tabs[i].scene.selection.borrow().view.vp_size.1;
|
||||
mag = if wpp > 0.0 {
|
||||
crate::entities::point::relative_world_size(0.0, wpp, viewport_height)
|
||||
} else {
|
||||
@@ -800,7 +800,7 @@ impl OpenCADStudio {
|
||||
self.grip_popup = None;
|
||||
return;
|
||||
}
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
@@ -1024,7 +1024,7 @@ impl OpenCADStudio {
|
||||
{
|
||||
return Task::none();
|
||||
}
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let (ox, oy) = match self.tabs[i]
|
||||
.scene
|
||||
.active_viewport
|
||||
@@ -1121,7 +1121,7 @@ impl OpenCADStudio {
|
||||
// slide the origin / rotate the axis. Short-circuits pan & snap.
|
||||
if let Some(kind) = self.ucs_grip_drag {
|
||||
self.drag_ucs_grip(i, kind, p);
|
||||
self.tabs[i].scene.selection.borrow_mut().last_move_pos = Some(p);
|
||||
self.tabs[i].scene.selection.borrow_mut().input.last_move_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
@@ -1129,7 +1129,7 @@ impl OpenCADStudio {
|
||||
// hit-area overlay and moves over the rest of the viewport.
|
||||
// `hover_id` returns None outside the cube box, which clears
|
||||
// any stale highlight from the previous `CursorMoved`.
|
||||
let (svw, svh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (svw, svh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let cube_tile = match self.tabs[i]
|
||||
.scene
|
||||
.active_viewport
|
||||
@@ -1148,7 +1148,7 @@ impl OpenCADStudio {
|
||||
VIEWCUBE_PX,
|
||||
));
|
||||
|
||||
let navigating = self.tabs[i].scene.selection.borrow().middle_down;
|
||||
let navigating = self.tabs[i].scene.selection.borrow().input.middle_down;
|
||||
if navigating {
|
||||
self.clear_navigation_hover(i);
|
||||
} else {
|
||||
@@ -1166,61 +1166,61 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.last_move_pos = Some(p);
|
||||
sel.input.last_move_pos = Some(p);
|
||||
|
||||
if sel.left_down {
|
||||
let press = sel.left_press_pos.unwrap_or(p);
|
||||
if sel.input.left_down {
|
||||
let press = sel.input.left_press_pos.unwrap_or(p);
|
||||
let dx = p.x - press.x;
|
||||
let dy = p.y - press.y;
|
||||
let dist2 = dx * dx + dy * dy;
|
||||
let elapsed_ms = sel
|
||||
.left_press_time
|
||||
.input.left_press_time
|
||||
.map(|t| Instant::now().duration_since(t).as_millis())
|
||||
.unwrap_or(u128::MAX);
|
||||
if !sel.left_dragging && elapsed_ms >= POLY_START_DELAY_MS && dist2 > 9.0 {
|
||||
sel.left_dragging = true;
|
||||
if !sel.input.left_dragging && elapsed_ms >= POLY_START_DELAY_MS && dist2 > 9.0 {
|
||||
sel.input.left_dragging = true;
|
||||
if self.pick_drag_rect {
|
||||
// PICKDRAG 1 (#226): press-drag spans a RECTANGLE
|
||||
// marquee — drive the existing box machinery (its
|
||||
// overlay and completion) instead of the lasso.
|
||||
sel.box_anchor = Some(press);
|
||||
sel.box_current = Some(p);
|
||||
if !sel.box_crossing_locked {
|
||||
sel.box_crossing = p.x < press.x;
|
||||
sel.gesture.box_anchor = Some(press);
|
||||
sel.gesture.box_current = Some(p);
|
||||
if !sel.gesture.box_crossing_locked {
|
||||
sel.gesture.box_crossing = p.x < press.x;
|
||||
}
|
||||
} else {
|
||||
sel.poly_active = true;
|
||||
sel.poly_crossing = p.x < press.x;
|
||||
sel.poly_points.clear();
|
||||
sel.poly_points.push(press);
|
||||
sel.poly_points.push(p);
|
||||
sel.gesture.poly_active = true;
|
||||
sel.gesture.poly_crossing = p.x < press.x;
|
||||
sel.gesture.poly_points.clear();
|
||||
sel.gesture.poly_points.push(press);
|
||||
sel.gesture.poly_points.push(p);
|
||||
}
|
||||
} else if sel.left_dragging && sel.poly_active {
|
||||
if sel.poly_points.last().map_or(true, |lp| {
|
||||
} else if sel.input.left_dragging && sel.gesture.poly_active {
|
||||
if sel.gesture.poly_points.last().map_or(true, |lp| {
|
||||
let ddx = p.x - lp.x;
|
||||
let ddy = p.y - lp.y;
|
||||
ddx * ddx + ddy * ddy > 16.0
|
||||
}) {
|
||||
sel.poly_points.push(p);
|
||||
sel.gesture.poly_points.push(p);
|
||||
}
|
||||
} else if sel.left_dragging {
|
||||
if let Some(a) = sel.box_anchor {
|
||||
sel.box_current = Some(p);
|
||||
if !sel.box_crossing_locked {
|
||||
sel.box_crossing = p.x < a.x;
|
||||
} else if sel.input.left_dragging {
|
||||
if let Some(a) = sel.gesture.box_anchor {
|
||||
sel.gesture.box_current = Some(p);
|
||||
if !sel.gesture.box_crossing_locked {
|
||||
sel.gesture.box_crossing = p.x < a.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if sel.box_anchor.is_some() {
|
||||
sel.box_current = Some(p);
|
||||
if let Some(a) = sel.box_anchor {
|
||||
if !sel.box_crossing_locked {
|
||||
sel.box_crossing = p.x < a.x;
|
||||
} else if sel.gesture.box_anchor.is_some() {
|
||||
sel.gesture.box_current = Some(p);
|
||||
if let Some(a) = sel.gesture.box_anchor {
|
||||
if !sel.gesture.box_crossing_locked {
|
||||
sel.gesture.box_crossing = p.x < a.x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let (mid_down, mid_last, vp_size) = (sel.middle_down, sel.middle_last_pos, sel.vp_size);
|
||||
let (mid_down, mid_last, vp_size) = (sel.input.middle_down, sel.input.middle_last_pos, sel.view.vp_size);
|
||||
if mid_down {
|
||||
if let Some(last) = mid_last {
|
||||
let (dx, dy) = (p.x - last.x, p.y - last.y);
|
||||
@@ -1257,9 +1257,9 @@ impl OpenCADStudio {
|
||||
// zone keeps the +/- glyph from strobing when a slow drag
|
||||
// wobbles a pixel either side of where it started.
|
||||
if dy.abs() >= ZOOM_DIR_FLIP_PX {
|
||||
sel.zoom_dir_out = dy > 0.0;
|
||||
sel.input.zoom_dir_out = dy > 0.0;
|
||||
}
|
||||
sel.middle_last_pos = Some(p);
|
||||
sel.input.middle_last_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
// Shift+MMB drag orbits the model view instead of panning
|
||||
@@ -1275,20 +1275,20 @@ impl OpenCADStudio {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_nav_perf(crate::scene::NavPerfOp::Rotate, move_started);
|
||||
self.tabs[i].scene.selection.borrow_mut().middle_last_pos = Some(p);
|
||||
self.tabs[i].scene.selection.borrow_mut().input.middle_last_pos = Some(p);
|
||||
return Task::none();
|
||||
} else if self.tabs[i].scene.current_layout == "Model" {
|
||||
if sel.orbit_pivot.is_none() {
|
||||
if sel.orbit.pivot.is_none() {
|
||||
let scene = &self.tabs[i].scene;
|
||||
let bounds = scene.active_model_tile_bounds(vp_size.0, vp_size.1);
|
||||
sel.orbit_pivot = Some(
|
||||
sel.orbit.pivot = Some(
|
||||
scene
|
||||
.orbit_pivot()
|
||||
.or_else(|| scene.view_center_surface_pivot(bounds))
|
||||
.unwrap_or_else(|| scene.camera.borrow().target),
|
||||
);
|
||||
}
|
||||
let pivot = sel.orbit_pivot;
|
||||
let pivot = sel.orbit.pivot;
|
||||
drop(sel);
|
||||
self.tabs[i].scene.refresh_projection_bounds();
|
||||
self.tabs[i].scene.camera.borrow_mut().orbit(dx, dy, pivot);
|
||||
@@ -1296,27 +1296,27 @@ impl OpenCADStudio {
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.record_nav_perf(crate::scene::NavPerfOp::Rotate, move_started);
|
||||
self.tabs[i].scene.selection.borrow_mut().middle_last_pos = Some(p);
|
||||
self.tabs[i].scene.selection.borrow_mut().input.middle_last_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
// Paper sheet is top-locked. Shift+MMB keeps its existing
|
||||
// pan fallback; the explicit orbit tool does nothing here.
|
||||
if self.tabs[i].orbit_mode {
|
||||
sel.middle_last_pos = Some(p);
|
||||
sel.input.middle_last_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
// Drop `sel` before calling mutable scene methods.
|
||||
drop(sel);
|
||||
self.pan_active_view(i, dx, dy, move_started);
|
||||
self.tabs[i].scene.selection.borrow_mut().middle_last_pos = Some(p);
|
||||
self.tabs[i].scene.selection.borrow_mut().input.middle_last_pos = Some(p);
|
||||
return Task::none();
|
||||
}
|
||||
sel.middle_last_pos = Some(p);
|
||||
sel.input.middle_last_pos = Some(p);
|
||||
}
|
||||
|
||||
let dragging = sel.left_down || sel.right_down || sel.middle_down;
|
||||
let vp_size = sel.vp_size;
|
||||
let dragging = sel.input.left_down || sel.input.right_down || sel.input.middle_down;
|
||||
let vp_size = sel.view.vp_size;
|
||||
drop(sel);
|
||||
|
||||
// The active pane already follows the cursor (each pane's own
|
||||
@@ -2593,7 +2593,7 @@ impl OpenCADStudio {
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.and_then(|c| c.window_first_corner());
|
||||
self.tabs[i].scene.selection.borrow_mut().preview_box = if is_window_corner {
|
||||
self.tabs[i].scene.selection.borrow_mut().gesture.preview_box = if is_window_corner {
|
||||
window_first.map(|c1| (proj(c1), p_full, true))
|
||||
} else {
|
||||
None
|
||||
@@ -2957,27 +2957,27 @@ impl OpenCADStudio {
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
}
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = false;
|
||||
sel.left_press_pos = None;
|
||||
sel.left_press_time = None;
|
||||
sel.left_dragging = false;
|
||||
sel.right_down = false;
|
||||
sel.right_press_pos = None;
|
||||
sel.right_press_time = None;
|
||||
sel.right_last_pos = None;
|
||||
sel.right_dragging = false;
|
||||
sel.right_click_entered = false;
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
sel.orbit_pivot = None;
|
||||
sel.box_anchor = None;
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.box_crossing = false;
|
||||
sel.box_crossing_locked = false;
|
||||
sel.poly_active = false;
|
||||
sel.poly_points.clear();
|
||||
sel.poly_crossing = false;
|
||||
sel.input.left_down = false;
|
||||
sel.input.left_press_pos = None;
|
||||
sel.input.left_press_time = None;
|
||||
sel.input.left_dragging = false;
|
||||
sel.input.right_down = false;
|
||||
sel.input.right_press_pos = None;
|
||||
sel.input.right_press_time = None;
|
||||
sel.input.right_last_pos = None;
|
||||
sel.input.right_dragging = false;
|
||||
sel.input.right_click_entered = false;
|
||||
sel.input.middle_down = false;
|
||||
sel.input.middle_last_pos = None;
|
||||
sel.orbit.pivot = None;
|
||||
sel.gesture.box_anchor = None;
|
||||
sel.gesture.box_anchor_world = None;
|
||||
sel.gesture.box_current = None;
|
||||
sel.gesture.box_crossing = false;
|
||||
sel.gesture.box_crossing_locked = false;
|
||||
sel.gesture.poly_active = false;
|
||||
sel.gesture.poly_points.clear();
|
||||
sel.gesture.poly_crossing = false;
|
||||
drop(sel);
|
||||
// Clear the rollover highlight when the cursor leaves the
|
||||
// viewport so it doesn't stick while the mouse is over the
|
||||
@@ -3045,7 +3045,7 @@ impl OpenCADStudio {
|
||||
world: glam::DVec3,
|
||||
from: Option<glam::DVec3>,
|
||||
) -> Option<crate::snap::SnapResult> {
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
if vw <= 1.0 || vh <= 1.0 {
|
||||
return None;
|
||||
}
|
||||
@@ -3088,7 +3088,7 @@ impl OpenCADStudio {
|
||||
/// or rotate the chosen axis to point at it, keeping a right-handed frame
|
||||
/// with Z fixed. Live — the commit (persist) happens on release.
|
||||
fn drag_ucs_grip(&mut self, i: usize, kind: crate::app::UcsGripKind, p_full: Point) {
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
if vw <= 1.0 || vh <= 1.0 {
|
||||
return;
|
||||
}
|
||||
@@ -3312,7 +3312,7 @@ impl OpenCADStudio {
|
||||
/// canvas-relative point the viewport handlers expect.
|
||||
pub(super) fn pane_canvas_point(&self, idx: usize, local: Point) -> Point {
|
||||
let i = self.active_tab;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let o = self.tabs[i].scene.pane_origin_px(idx, vw, vh);
|
||||
Point::new(o.x + local.x, o.y + local.y)
|
||||
}
|
||||
@@ -3323,7 +3323,7 @@ impl OpenCADStudio {
|
||||
) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.model_panes.resize(ev.split, ev.ratio);
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
self.tabs[i].scene.sync_tiles_from_panes(vw, vh);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
Task::none()
|
||||
@@ -3344,7 +3344,7 @@ impl OpenCADStudio {
|
||||
if let iced::widget::pane_grid::DragEvent::Dropped { pane, target } = ev {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.model_panes.drop(pane, target);
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
self.tabs[i].scene.sync_tiles_from_panes(vw, vh);
|
||||
self.tabs[i].scene.camera_generation += 1;
|
||||
}
|
||||
@@ -3362,7 +3362,7 @@ impl OpenCADStudio {
|
||||
if self.tabs[i].scene.current_layout != "Model" {
|
||||
return None;
|
||||
}
|
||||
let vp_size = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let vp_size = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let scope = self.tabs[i].current_parametric_scope();
|
||||
let id = self.tabs[i].scene.constraint_glyph_hit(
|
||||
scope,
|
||||
@@ -3391,7 +3391,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow_mut()
|
||||
.right_click_entered = false;
|
||||
.input.right_click_entered = false;
|
||||
// A click in the viewport dismisses any open ribbon dropdown
|
||||
// (e.g. the annotation style combo), which has no backdrop of
|
||||
// its own to catch outside clicks.
|
||||
@@ -3419,7 +3419,7 @@ impl OpenCADStudio {
|
||||
// reaches here is outside the menu.
|
||||
{
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
if sel.context_menu.take().is_some() {
|
||||
if sel.menu.open_at.take().is_some() {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
@@ -3441,11 +3441,11 @@ impl OpenCADStudio {
|
||||
}
|
||||
let (p, vp_size) = {
|
||||
let sel = self.tabs[i].scene.selection.borrow();
|
||||
let p = match sel.last_move_pos {
|
||||
let p = match sel.input.last_move_pos {
|
||||
Some(p) => p,
|
||||
None => return Task::none(),
|
||||
};
|
||||
(p, sel.vp_size)
|
||||
(p, sel.view.vp_size)
|
||||
};
|
||||
let (vw, vh) = vp_size;
|
||||
|
||||
@@ -3488,16 +3488,16 @@ impl OpenCADStudio {
|
||||
self.clear_navigation_hover(i);
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = true;
|
||||
sel.middle_last_pos = Some(p);
|
||||
sel.input.middle_down = true;
|
||||
sel.input.middle_last_pos = Some(p);
|
||||
if self.tabs[i].zoom_dynamic_mode {
|
||||
sel.box_anchor = Some(p);
|
||||
sel.box_current = Some(p);
|
||||
sel.box_crossing = false;
|
||||
sel.box_crossing_locked = true;
|
||||
sel.gesture.box_anchor = Some(p);
|
||||
sel.gesture.box_current = Some(p);
|
||||
sel.gesture.box_crossing = false;
|
||||
sel.gesture.box_crossing_locked = true;
|
||||
// A fresh drag starts neutral: the magnifier shows "+" until
|
||||
// the pointer has actually moved far enough to mean zoom out.
|
||||
sel.zoom_dir_out = false;
|
||||
sel.input.zoom_dir_out = false;
|
||||
}
|
||||
return Task::none();
|
||||
}
|
||||
@@ -3743,15 +3743,15 @@ impl OpenCADStudio {
|
||||
// Selection steps and drag-fence picks still wait for the release.
|
||||
let picks_on_press = self.tabs[i].active_cmd.as_ref().is_some_and(|command| {
|
||||
!command.is_selection_gathering() && !command.accepts_drag_selection()
|
||||
}) && self.tabs[i].scene.selection.borrow().box_anchor.is_none();
|
||||
}) && self.tabs[i].scene.selection.borrow().gesture.box_anchor.is_none();
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = true;
|
||||
sel.input.left_down = true;
|
||||
// Stored in full-canvas space (like ViewportMove's cursor and
|
||||
// the overlay box / lasso drawing); release maps it into the
|
||||
// active tile. Tile-local here would double-offset the anchor.
|
||||
sel.left_press_pos = Some(p_full);
|
||||
sel.left_press_time = Some(Instant::now());
|
||||
sel.left_dragging = false;
|
||||
sel.input.left_press_pos = Some(p_full);
|
||||
sel.input.left_press_time = Some(Instant::now());
|
||||
sel.input.left_dragging = false;
|
||||
drop(sel);
|
||||
if picks_on_press {
|
||||
// The command path clears `left_down`, so the real release
|
||||
@@ -3776,14 +3776,14 @@ impl OpenCADStudio {
|
||||
// left drag (exit is Esc / another command). Mirror of the press.
|
||||
if self.tabs[i].orbit_mode || self.tabs[i].pan_mode || self.tabs[i].zoom_dynamic_mode {
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.middle_down = false;
|
||||
sel.middle_last_pos = None;
|
||||
sel.orbit_pivot = None;
|
||||
sel.box_anchor = None;
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.box_crossing_locked = false;
|
||||
sel.zoom_dir_out = false;
|
||||
sel.input.middle_down = false;
|
||||
sel.input.middle_last_pos = None;
|
||||
sel.orbit.pivot = None;
|
||||
sel.gesture.box_anchor = None;
|
||||
sel.gesture.box_anchor_world = None;
|
||||
sel.gesture.box_current = None;
|
||||
sel.gesture.box_crossing_locked = false;
|
||||
sel.input.zoom_dir_out = false;
|
||||
drop(sel);
|
||||
self.arm_hover_after_navigation(i);
|
||||
return Task::none();
|
||||
@@ -3797,19 +3797,19 @@ impl OpenCADStudio {
|
||||
self.tabs[i].snap_result = None;
|
||||
self.snapper.from_point = None;
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = false;
|
||||
sel.left_press_pos = None;
|
||||
sel.left_dragging = false;
|
||||
sel.input.left_down = false;
|
||||
sel.input.left_press_pos = None;
|
||||
sel.input.left_dragging = false;
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let (p, is_click, is_down) = {
|
||||
let sel = self.tabs[i].scene.selection.borrow();
|
||||
let p = match sel.last_move_pos {
|
||||
let p = match sel.input.last_move_pos {
|
||||
Some(p) => p,
|
||||
None => return Task::none(),
|
||||
};
|
||||
(p, !sel.left_dragging, sel.left_down)
|
||||
(p, !sel.input.left_dragging, sel.input.left_down)
|
||||
};
|
||||
|
||||
// Grip editing: click-move-click (plus legacy press-drag).
|
||||
@@ -3850,7 +3850,7 @@ impl OpenCADStudio {
|
||||
// Inside a floating viewport the pane is the viewport's own rect
|
||||
// and camera (see the MoveCursor path); pick / snap then run in
|
||||
// model space exactly where the GPU draws the content.
|
||||
let canvas_sz = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let canvas_sz = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let edit_frame = self.tabs[i].scene.viewport_edit_frame(canvas_sz);
|
||||
let (tile_vw, tile_vh, tile_off) = match &edit_frame {
|
||||
Some((_, full)) => (full.width, full.height, iced::Point::new(full.x, full.y)),
|
||||
@@ -3872,7 +3872,7 @@ impl OpenCADStudio {
|
||||
.as_ref()
|
||||
.map(|c| c.is_selection_gathering())
|
||||
.unwrap_or(false);
|
||||
let selection_box_active = self.tabs[i].scene.selection.borrow().box_anchor.is_some();
|
||||
let selection_box_active = self.tabs[i].scene.selection.borrow().gesture.box_anchor.is_some();
|
||||
let selection_pick_add = self.pick_add
|
||||
|| self.tabs[i]
|
||||
.active_cmd
|
||||
@@ -4523,11 +4523,11 @@ impl OpenCADStudio {
|
||||
{
|
||||
let anchor_world = self.cursor_model_point(i, &edit_cam, p, bounds);
|
||||
let mut selection = self.tabs[i].scene.selection.borrow_mut();
|
||||
selection.box_anchor = Some(p_full);
|
||||
selection.box_current = Some(p_full);
|
||||
selection.box_anchor_world = Some(anchor_world);
|
||||
if !selection.box_crossing_locked {
|
||||
selection.box_crossing = false;
|
||||
selection.gesture.box_anchor = Some(p_full);
|
||||
selection.gesture.box_current = Some(p_full);
|
||||
selection.gesture.box_anchor_world = Some(anchor_world);
|
||||
if !selection.gesture.box_crossing_locked {
|
||||
selection.gesture.box_crossing = false;
|
||||
}
|
||||
None
|
||||
} else {
|
||||
@@ -4632,29 +4632,29 @@ impl OpenCADStudio {
|
||||
if let Some(r) = result {
|
||||
let task = self.apply_cmd_result(r);
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = false;
|
||||
sel.left_press_pos = None;
|
||||
sel.left_press_time = None;
|
||||
sel.left_dragging = false;
|
||||
sel.input.left_down = false;
|
||||
sel.input.left_press_pos = None;
|
||||
sel.input.left_press_time = None;
|
||||
sel.input.left_dragging = false;
|
||||
return task;
|
||||
}
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = false;
|
||||
sel.left_press_pos = None;
|
||||
sel.left_press_time = None;
|
||||
sel.left_dragging = false;
|
||||
sel.input.left_down = false;
|
||||
sel.input.left_press_pos = None;
|
||||
sel.input.left_press_time = None;
|
||||
sel.input.left_dragging = false;
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
let (is_down2, is_dragging, box_anchor, box_crossing, _vp_size, poly_drag) = {
|
||||
let sel = self.tabs[i].scene.selection.borrow();
|
||||
(
|
||||
sel.left_down,
|
||||
sel.left_dragging,
|
||||
sel.box_anchor,
|
||||
sel.box_crossing,
|
||||
sel.vp_size,
|
||||
sel.poly_active,
|
||||
sel.input.left_down,
|
||||
sel.input.left_dragging,
|
||||
sel.gesture.box_anchor,
|
||||
sel.gesture.box_crossing,
|
||||
sel.view.vp_size,
|
||||
sel.gesture.poly_active,
|
||||
)
|
||||
};
|
||||
|
||||
@@ -4683,7 +4683,7 @@ impl OpenCADStudio {
|
||||
.scene
|
||||
.selection
|
||||
.borrow()
|
||||
.poly_points
|
||||
.gesture.poly_points
|
||||
.iter()
|
||||
.map(|point| iced::Point {
|
||||
x: point.x - tile_off.x,
|
||||
@@ -4749,18 +4749,18 @@ impl OpenCADStudio {
|
||||
});
|
||||
if let Some(result) = result {
|
||||
let mut selection = self.tabs[i].scene.selection.borrow_mut();
|
||||
selection.left_down = false;
|
||||
selection.left_press_pos = None;
|
||||
selection.left_press_time = None;
|
||||
selection.left_dragging = false;
|
||||
selection.poly_active = false;
|
||||
selection.poly_points.clear();
|
||||
selection.poly_crossing = false;
|
||||
selection.box_anchor = None;
|
||||
selection.box_anchor_world = None;
|
||||
selection.box_current = None;
|
||||
selection.box_crossing = false;
|
||||
selection.box_crossing_locked = false;
|
||||
selection.input.left_down = false;
|
||||
selection.input.left_press_pos = None;
|
||||
selection.input.left_press_time = None;
|
||||
selection.input.left_dragging = false;
|
||||
selection.gesture.poly_active = false;
|
||||
selection.gesture.poly_points.clear();
|
||||
selection.gesture.poly_crossing = false;
|
||||
selection.gesture.box_anchor = None;
|
||||
selection.gesture.box_anchor_world = None;
|
||||
selection.gesture.box_current = None;
|
||||
selection.gesture.box_crossing = false;
|
||||
selection.gesture.box_crossing_locked = false;
|
||||
drop(selection);
|
||||
return self.apply_cmd_result(result);
|
||||
}
|
||||
@@ -4896,16 +4896,15 @@ properties={:.1}ms picked={}",
|
||||
let sel = self.tabs[i].scene.selection.borrow();
|
||||
// Map lasso points into the active tile.
|
||||
let pts: Vec<iced::Point> = sel
|
||||
.poly_points
|
||||
.gesture.poly_points
|
||||
.iter()
|
||||
.map(|pp| iced::Point {
|
||||
x: pp.x - tile_off.x,
|
||||
y: pp.y - tile_off.y,
|
||||
})
|
||||
.collect();
|
||||
(pts, sel.poly_crossing)
|
||||
(pts, sel.gesture.poly_crossing)
|
||||
};
|
||||
self.tabs[i].scene.selection.borrow_mut().poly_last_crossing = crossing;
|
||||
let (view_rot, eye, all_wires) = self.pick_view(i, &edit_cam, bounds);
|
||||
let handles = self.tabs[i].scene.path_hit_handles(
|
||||
&poly_pts,
|
||||
@@ -4937,12 +4936,12 @@ properties={:.1}ms picked={}",
|
||||
selection_just_completed = true;
|
||||
}
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.poly_active = false;
|
||||
sel.poly_points.clear();
|
||||
sel.poly_crossing = false;
|
||||
sel.box_anchor = None;
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.gesture.poly_active = false;
|
||||
sel.gesture.poly_points.clear();
|
||||
sel.gesture.poly_crossing = false;
|
||||
sel.gesture.box_anchor = None;
|
||||
sel.gesture.box_anchor_world = None;
|
||||
sel.gesture.box_current = None;
|
||||
} else {
|
||||
if box_anchor.is_none() {
|
||||
// A parametric-constraint glyph draws above its geometry
|
||||
@@ -5121,11 +5120,11 @@ was_selected={}",
|
||||
// Full-canvas space: ViewportMove updates
|
||||
// box_current in canvas coords and the overlay
|
||||
// draws there; release maps back into the tile.
|
||||
sel.box_anchor = Some(p_full);
|
||||
sel.box_current = Some(p_full);
|
||||
sel.box_anchor_world = Some(anchor_world);
|
||||
if !sel.box_crossing_locked {
|
||||
sel.box_crossing = false;
|
||||
sel.gesture.box_anchor = Some(p_full);
|
||||
sel.gesture.box_current = Some(p_full);
|
||||
sel.gesture.box_anchor_world = Some(anchor_world);
|
||||
if !sel.gesture.box_crossing_locked {
|
||||
sel.gesture.box_crossing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5165,15 +5164,15 @@ was_selected={}",
|
||||
});
|
||||
if let Some(result) = command_result {
|
||||
let mut selection = self.tabs[i].scene.selection.borrow_mut();
|
||||
selection.left_down = false;
|
||||
selection.left_press_pos = None;
|
||||
selection.left_press_time = None;
|
||||
selection.left_dragging = false;
|
||||
selection.box_anchor = None;
|
||||
selection.box_anchor_world = None;
|
||||
selection.box_current = None;
|
||||
selection.box_crossing = false;
|
||||
selection.box_crossing_locked = false;
|
||||
selection.input.left_down = false;
|
||||
selection.input.left_press_pos = None;
|
||||
selection.input.left_press_time = None;
|
||||
selection.input.left_dragging = false;
|
||||
selection.gesture.box_anchor = None;
|
||||
selection.gesture.box_anchor_world = None;
|
||||
selection.gesture.box_current = None;
|
||||
selection.gesture.box_crossing = false;
|
||||
selection.gesture.box_crossing_locked = false;
|
||||
drop(selection);
|
||||
return self.apply_cmd_result(result);
|
||||
}
|
||||
@@ -5311,22 +5310,20 @@ properties={:.1}ms picked={}",
|
||||
);
|
||||
}
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.box_last = Some((a, p));
|
||||
sel.box_last_crossing = crossing;
|
||||
sel.box_anchor = None;
|
||||
sel.box_anchor_world = None;
|
||||
sel.box_current = None;
|
||||
sel.box_crossing = false;
|
||||
sel.box_crossing_locked = false;
|
||||
sel.gesture.box_anchor = None;
|
||||
sel.gesture.box_anchor_world = None;
|
||||
sel.gesture.box_current = None;
|
||||
sel.gesture.box_crossing = false;
|
||||
sel.gesture.box_crossing_locked = false;
|
||||
selection_just_completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
sel.left_down = false;
|
||||
sel.left_press_pos = None;
|
||||
sel.left_press_time = None;
|
||||
sel.left_dragging = false;
|
||||
sel.input.left_down = false;
|
||||
sel.input.left_press_pos = None;
|
||||
sel.input.left_press_time = None;
|
||||
sel.input.left_dragging = false;
|
||||
}
|
||||
|
||||
if selection_just_completed {
|
||||
@@ -5364,7 +5361,7 @@ properties={:.1}ms picked={}",
|
||||
self.last_vp_click_pos = Some(p);
|
||||
|
||||
if is_double_model {
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
@@ -5513,7 +5510,7 @@ properties={:.1}ms picked={}",
|
||||
self.last_vp_click_pos = Some(p);
|
||||
|
||||
if is_double {
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
@@ -5660,7 +5657,7 @@ properties={:.1}ms picked={}",
|
||||
}
|
||||
let (cursor, canvas_size) = {
|
||||
let selection = self.tabs[i].scene.selection.borrow();
|
||||
(selection.last_move_pos, selection.vp_size)
|
||||
(selection.input.last_move_pos, selection.view.vp_size)
|
||||
};
|
||||
let Some(cursor) = cursor else {
|
||||
return;
|
||||
@@ -5690,18 +5687,18 @@ properties={:.1}ms picked={}",
|
||||
let now = Instant::now();
|
||||
let is_double = {
|
||||
let sel = self.tabs[i].scene.selection.borrow();
|
||||
sel.middle_last_press_time
|
||||
sel.input.middle_last_press_time
|
||||
.map(|t| now.duration_since(t).as_millis() < 300)
|
||||
.unwrap_or(false)
|
||||
};
|
||||
{
|
||||
let mut sel = self.tabs[i].scene.selection.borrow_mut();
|
||||
let Some(p) = sel.last_move_pos else {
|
||||
let Some(p) = sel.input.last_move_pos else {
|
||||
return Task::none();
|
||||
};
|
||||
sel.middle_down = true;
|
||||
sel.middle_last_pos = Some(p);
|
||||
sel.middle_last_press_time = Some(now);
|
||||
sel.input.middle_down = true;
|
||||
sel.input.middle_last_pos = Some(p);
|
||||
sel.input.middle_last_press_time = Some(now);
|
||||
}
|
||||
if is_double {
|
||||
self.tabs[i].scene.fit_all();
|
||||
@@ -5752,7 +5749,7 @@ properties={:.1}ms picked={}",
|
||||
fn pan_active_view(&mut self, i: usize, dx: f32, dy: f32, started: Instant) {
|
||||
// Pan scale uses the active tile's size (ortho size is relative to
|
||||
// viewport height), so a tiled pane pans at the correct rate.
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let bounds = self.tabs[i].scene.active_model_tile_bounds(vw, vh);
|
||||
if self.tabs[i].scene.active_viewport.is_some() {
|
||||
self.tabs[i].scene.pan_active_viewport(dx, dy, bounds);
|
||||
@@ -5786,8 +5783,8 @@ properties={:.1}ms picked={}",
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.remember_current_view();
|
||||
self.clear_navigation_hover(i);
|
||||
let cursor = self.tabs[i].scene.selection.borrow().last_move_pos;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let cursor = self.tabs[i].scene.selection.borrow().input.last_move_pos;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let bounds = iced::Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
@@ -5847,7 +5844,7 @@ properties={:.1}ms picked={}",
|
||||
/// drawing instead of staying frozen at its original pixel. No-op when no
|
||||
/// box is in progress. (#234)
|
||||
pub(in crate::app) fn reproject_box_anchor(&mut self, i: usize, vw: f32, vh: f32) {
|
||||
let world = self.tabs[i].scene.selection.borrow().box_anchor_world;
|
||||
let world = self.tabs[i].scene.selection.borrow().gesture.box_anchor_world;
|
||||
let Some(world) = world else { return };
|
||||
let tile_b = self.tabs[i].scene.active_model_tile_bounds(vw, vh);
|
||||
let tile_local = iced::Rectangle {
|
||||
@@ -5862,7 +5859,7 @@ properties={:.1}ms picked={}",
|
||||
.borrow()
|
||||
.project(world, tile_local)
|
||||
{
|
||||
self.tabs[i].scene.selection.borrow_mut().box_anchor =
|
||||
self.tabs[i].scene.selection.borrow_mut().gesture.box_anchor =
|
||||
Some(iced::Point::new(sp.x + tile_b.x, sp.y + tile_b.y));
|
||||
}
|
||||
}
|
||||
@@ -5878,7 +5875,7 @@ properties={:.1}ms picked={}",
|
||||
return Task::none();
|
||||
}
|
||||
let rot = self.tabs[i].scene.active_view_rotation_mat();
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
// The ViewCube draws in the top-right of whichever area
|
||||
// owns it: the full canvas in model space, or the active
|
||||
// viewport's screen rectangle in a paper layout. Map the
|
||||
@@ -6018,12 +6015,12 @@ properties={:.1}ms picked={}",
|
||||
let deferred_command = self.tabs[i].active_cmd.as_ref().is_some_and(|command| {
|
||||
command.needs_entity_pick() && command.entity_pick_deferred_hover()
|
||||
});
|
||||
let navigating = self.tabs[i].scene.selection.borrow().middle_down;
|
||||
let navigating = self.tabs[i].scene.selection.borrow().input.middle_down;
|
||||
if (self.tabs[i].active_cmd.is_some() && !deferred_command) || navigating {
|
||||
self.clear_navigation_hover(i);
|
||||
return Task::none();
|
||||
}
|
||||
let cursor = self.tabs[i].scene.selection.borrow().last_move_pos;
|
||||
let cursor = self.tabs[i].scene.selection.borrow().input.last_move_pos;
|
||||
self.constraint_glyph_tooltip = cursor.and_then(|point| {
|
||||
self.constraint_glyph_under(i, point).map(|(kind, _)| kind)
|
||||
});
|
||||
@@ -6037,7 +6034,7 @@ properties={:.1}ms picked={}",
|
||||
// Inside a viewport, hover-pick through the viewport camera +
|
||||
// model wires so the rollover highlights the entity under the
|
||||
// cursor (dwell.point / dwell.tile_size are already pane-local).
|
||||
let canvas_sz = self.tabs[i].scene.selection.borrow().vp_size;
|
||||
let canvas_sz = self.tabs[i].scene.selection.borrow().view.vp_size;
|
||||
let edit_cam = self.tabs[i]
|
||||
.scene
|
||||
.viewport_edit_frame(canvas_sz)
|
||||
@@ -6818,7 +6815,7 @@ mod zoom_dynamic_cursor_tests {
|
||||
fn zoom_armed_app() -> OpenCADStudio {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
app.tabs[0].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[0].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let _ = app.dispatch_command("ZOOM DYNAMIC");
|
||||
app
|
||||
}
|
||||
@@ -6828,7 +6825,7 @@ mod zoom_dynamic_cursor_tests {
|
||||
}
|
||||
|
||||
fn zoom_dir_out(app: &OpenCADStudio) -> bool {
|
||||
app.tabs[0].scene.selection.borrow().zoom_dir_out
|
||||
app.tabs[0].scene.selection.borrow().input.zoom_dir_out
|
||||
}
|
||||
|
||||
/// The magnifier has to carry the sign of the zoom actually under way:
|
||||
@@ -6946,7 +6943,7 @@ mod selection_preview_tests {
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.constraint_solve_mode = true;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let scene = &mut app.tabs[i].scene;
|
||||
let [outer, inner, upper, lower] = [
|
||||
([0.0, 0.0], [0.0, 5.0]),
|
||||
@@ -6987,7 +6984,7 @@ mod selection_preview_tests {
|
||||
let handles = if let Ok(path) = std::env::var("OCS_GRIP_TEST_DRAWING") {
|
||||
let result = app.automation_op(&serde_json::json!({"op":"open","path":path}).to_string());
|
||||
assert_eq!(result["ok"], true, "{result}");
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
[0x57F, 0x57D, 0x57C, 0x57A, 0x57E, 0x57B].map(Handle::new)
|
||||
} else {
|
||||
[outer, inner, upper, lower, top, right]
|
||||
@@ -7067,7 +7064,7 @@ mod selection_preview_tests {
|
||||
app.snapper.otrack_enabled = false;
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let a = app.tabs[i]
|
||||
.scene
|
||||
.add_entity(EntityType::Line(Line::from_points(
|
||||
@@ -7148,7 +7145,7 @@ mod selection_preview_tests {
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.constraint_solve_mode = true;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let mut rectangle = LwPolyline::from_points(vec![
|
||||
Vector2::new(0.0, 0.0),
|
||||
Vector2::new(4.0, 0.0),
|
||||
@@ -7165,7 +7162,7 @@ mod selection_preview_tests {
|
||||
let handle = if let Ok(path) = std::env::var("OCS_GRIP_TEST_DRAWING") {
|
||||
let result = app.automation_op(&serde_json::json!({"op":"open","path":path}).to_string());
|
||||
assert_eq!(result["ok"], true, "{result}");
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
Handle::new(0x556)
|
||||
} else {
|
||||
handle
|
||||
@@ -7236,7 +7233,7 @@ mod selection_preview_tests {
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.constraint_solve_mode = true;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let end = if vertical { Vector3::new(0.0, 4.0, 0.0) } else { Vector3::new(4.0, 0.0, 0.0) };
|
||||
let handle = app.tabs[i].scene.add_entity(EntityType::Line(Line::from_points(Vector3::ZERO, end)));
|
||||
app.tabs[i].scene.parametric_constraint_set_mut(ParametricScope::ModelSpace).add(
|
||||
@@ -7263,7 +7260,7 @@ mod selection_preview_tests {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let bounds = iced::Rectangle::with_size(iced::Size::new(800.0, 600.0));
|
||||
let cursor = |app: &OpenCADStudio, x: f64, y: f64| {
|
||||
let at = app.tabs[i].scene.camera.borrow().project(glam::DVec3::new(x, y, 0.0), bounds);
|
||||
@@ -7301,7 +7298,7 @@ mod selection_preview_tests {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
// Base away from the model origin so the UCS icon cannot swallow
|
||||
// the press (it sits at the origin in a fresh drawing).
|
||||
let handle = app.tabs[i].scene.add_entity(EntityType::XLine(
|
||||
@@ -7317,7 +7314,7 @@ mod selection_preview_tests {
|
||||
let bounds = iced::Rectangle::with_size(iced::Size::new(800.0, 600.0));
|
||||
let g1world = app.tabs[i].selected_grips[1].world;
|
||||
let g1cursor = app.tabs[i].scene.camera.borrow().project(g1world, bounds).unwrap();
|
||||
app.tabs[i].scene.selection.borrow_mut().last_move_pos =
|
||||
app.tabs[i].scene.selection.borrow_mut().input.last_move_pos =
|
||||
Some(iced::Point::new(g1cursor.x, g1cursor.y));
|
||||
let _ = app.on_viewport_left_press();
|
||||
assert_eq!(
|
||||
@@ -7345,7 +7342,7 @@ mod selection_preview_tests {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let handle = app.tabs[i].scene.add_entity(EntityType::Line(Line::from_points(
|
||||
Vector3::new(-1.0, 0.0, 0.0),
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
@@ -7403,9 +7400,9 @@ mod selection_preview_tests {
|
||||
record.add_value(XDataValue::String("https://example.com/linked".into()));
|
||||
line.common.extended_data.add_record(record);
|
||||
let handle = app.tabs[i].scene.add_entity(EntityType::Line(line));
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let _ = app.run_command_line("ZOOM EXTENTS");
|
||||
app.tabs[i].scene.selection.borrow_mut().last_move_pos = Some(Point::new(400.0, 300.0));
|
||||
app.tabs[i].scene.selection.borrow_mut().input.last_move_pos = Some(Point::new(400.0, 300.0));
|
||||
app.ctrl_down = mode != "plain";
|
||||
match mode {
|
||||
"pan" => app.tabs[i].pan_mode = true,
|
||||
@@ -7429,9 +7426,9 @@ mod selection_preview_tests {
|
||||
if mode == "link" {
|
||||
assert!(task.units() > 0);
|
||||
let selection = app.tabs[i].scene.selection.borrow();
|
||||
assert!(!selection.left_down && selection.box_anchor.is_none());
|
||||
assert!(!selection.input.left_down && selection.gesture.box_anchor.is_none());
|
||||
} else if matches!(mode, "pan" | "orbit" | "zoom") {
|
||||
assert!(app.tabs[i].scene.selection.borrow().middle_down, "{mode}");
|
||||
assert!(app.tabs[i].scene.selection.borrow().input.middle_down, "{mode}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7444,7 +7441,7 @@ mod selection_preview_tests {
|
||||
let i = app.active_tab;
|
||||
app.model_space.selection_preview = preview;
|
||||
let _ = app.run_command_line("LINE 0,0 10,10");
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let _ = app.run_command_line("ZOOM EXTENTS");
|
||||
|
||||
// The pick only runs once the cursor has been still for the dwell
|
||||
@@ -7495,7 +7492,7 @@ mod selection_preview_tests {
|
||||
app.snapper.otrack_enabled = false;
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.add_entity(EntityType::Line(Line::from_points(
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
Vector3::new(10.0, 0.0, 25.0),
|
||||
@@ -7550,7 +7547,7 @@ mod selection_preview_tests {
|
||||
app.snapper.otrack_enabled = false;
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
|
||||
// In-app BOX equivalent: kernel box committed as a SAT solid.
|
||||
let base =
|
||||
@@ -7644,7 +7641,7 @@ mod selection_preview_tests {
|
||||
app.snapper.otrack_enabled = false;
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
|
||||
let base =
|
||||
crate::scene::model::solid_model::box_solid([0.0, 0.0, 2.5], 10.0, 10.0, 5.0)
|
||||
@@ -7712,7 +7709,7 @@ mod selection_preview_tests {
|
||||
app.snapper.otrack_enabled = false;
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
|
||||
let base =
|
||||
crate::scene::model::solid_model::box_solid([0.0, 0.0, 2.5], 10.0, 10.0, 5.0)
|
||||
@@ -7779,7 +7776,7 @@ mod selection_preview_tests {
|
||||
app.snapper.otrack_enabled = false;
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
|
||||
let base =
|
||||
crate::scene::model::solid_model::box_solid([0.0, 0.0, 2.5], 10.0, 10.0, 5.0)
|
||||
@@ -7840,7 +7837,7 @@ mod selection_preview_tests {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
app.automation_op(r#"{"op":"new"}"#);
|
||||
let i = app.active_tab;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
|
||||
let base =
|
||||
crate::scene::model::solid_model::box_solid([0.0, 0.0, 2.5], 10.0, 10.0, 5.0)
|
||||
@@ -7925,7 +7922,7 @@ mod selection_preview_tests {
|
||||
app.snapper.otrack_enabled = false;
|
||||
app.ortho_mode = false;
|
||||
app.polar_mode = false;
|
||||
app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
app.tabs[i].scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
|
||||
let base =
|
||||
crate::scene::model::solid_model::box_solid([0.0, 0.0, 2.5], 10.0, 10.0, 5.0)
|
||||
|
||||
+20
-20
@@ -263,13 +263,13 @@ impl OpenCADStudio {
|
||||
tab.scene
|
||||
.active_viewport
|
||||
.and_then(|h| {
|
||||
let (cw, ch) = tab.scene.selection.borrow().vp_size;
|
||||
let (cw, ch) = tab.scene.selection.borrow().view.vp_size;
|
||||
tab.scene.viewport_screen_rect(h, (cw, ch))
|
||||
})
|
||||
.map(|r| r.width)
|
||||
.unwrap_or(f32::INFINITY)
|
||||
} else {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = tab.scene.selection.borrow().view.vp_size;
|
||||
tab.scene.active_model_tile_bounds(vw, vh).width
|
||||
};
|
||||
let viewcube_visible = self.show_viewcube
|
||||
@@ -351,7 +351,7 @@ impl OpenCADStudio {
|
||||
let size_probe: Element<'_, Message> = responsive(move |size| {
|
||||
{
|
||||
let mut sel = scene.selection.borrow_mut();
|
||||
sel.vp_size = (size.width, size.height);
|
||||
sel.view.vp_size = (size.width, size.height);
|
||||
}
|
||||
scene.sync_tiles_from_panes(size.width, size.height);
|
||||
Space::new().width(Fill).height(Fill)
|
||||
@@ -406,7 +406,7 @@ impl OpenCADStudio {
|
||||
let grid_overlay = if self.layout_settling || tab.is_start {
|
||||
Space::new().into()
|
||||
} else {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = tab.scene.selection.borrow().view.vp_size;
|
||||
let model_basis = {
|
||||
let (o, ux, uy, uz) = tab.ucs_xform().axes();
|
||||
let (ux, uy, uz) = super::helpers::drafting_axes(
|
||||
@@ -527,7 +527,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
let grips: Vec<crate::ui::overlay::GripMarker> = if show_grips
|
||||
&& !tab.selected_grips.is_empty()
|
||||
{
|
||||
let (vw, vh) = sel_ref.vp_size;
|
||||
let (vw, vh) = sel_ref.view.vp_size;
|
||||
// Overlays project through the active tile's camera, so
|
||||
// they must use the active tile's screen rectangle (with
|
||||
// its canvas offset) — not the whole canvas — or they
|
||||
@@ -645,7 +645,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
let grip_clip = if grips.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let (vw, vh) = sel_ref.vp_size;
|
||||
let (vw, vh) = sel_ref.view.vp_size;
|
||||
Some(
|
||||
tab.scene
|
||||
.viewport_edit_frame((vw, vh))
|
||||
@@ -654,7 +654,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
)
|
||||
};
|
||||
|
||||
let (vw, vh) = sel_ref.vp_size;
|
||||
let (vw, vh) = sel_ref.view.vp_size;
|
||||
// Active tile rectangle (canvas-offset included) so grid / UCS
|
||||
// icon / crosshair project through the active pane's camera at
|
||||
// the correct place and scale.
|
||||
@@ -814,7 +814,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
|
||||
// Model-space pane dividers (none in paper / single-pane layouts).
|
||||
let dividers = if !is_paper {
|
||||
let (vw, vh) = sel_ref.vp_size;
|
||||
let (vw, vh) = sel_ref.view.vp_size;
|
||||
tab.scene.model_pane_dividers(vw, vh)
|
||||
} else {
|
||||
vec![]
|
||||
@@ -824,8 +824,8 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
// target pane under the cursor.
|
||||
let (pane_move_rect, pane_drop_rect) = match self.pane_move_from {
|
||||
Some(from) if !is_paper => {
|
||||
let (vw, vh) = sel_ref.vp_size;
|
||||
let cursor = sel_ref.last_move_pos;
|
||||
let (vw, vh) = sel_ref.view.vp_size;
|
||||
let cursor = sel_ref.input.last_move_pos;
|
||||
let tiles = tab.scene.model_tiles.borrow();
|
||||
let px = |t: &crate::scene::ModelTile| iced::Rectangle {
|
||||
x: t.rect.x * vw,
|
||||
@@ -882,7 +882,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
let scope = tab.current_parametric_scope();
|
||||
tab.scene.cached_glyph_placements(
|
||||
scope,
|
||||
sel_ref.vp_size,
|
||||
sel_ref.view.vp_size,
|
||||
self.show_constraint_values,
|
||||
self.constraint_bar_display,
|
||||
self.constraint_bar_mode,
|
||||
@@ -1009,7 +1009,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
.as_ref()
|
||||
.and_then(|command| command.dyn_label_point(w))
|
||||
.and_then(|world| {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = tab.scene.selection.borrow().view.vp_size;
|
||||
let (camera, bounds) =
|
||||
tab.scene.viewport_edit_frame((vw, vh)).unwrap_or_else(|| {
|
||||
(
|
||||
@@ -1085,7 +1085,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
opposite + width_axis * width + height_axis * (height * 0.5);
|
||||
let rectangle_center =
|
||||
opposite + width_axis * (width * 0.5) + height_axis * (height * 0.5);
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = tab.scene.selection.borrow().view.vp_size;
|
||||
let (camera, bounds) =
|
||||
tab.scene.viewport_edit_frame((vw, vh)).unwrap_or_else(|| {
|
||||
(
|
||||
@@ -1282,7 +1282,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
// viewport drawing / selection is unaffected. In a paper layout
|
||||
// the active viewport gets its own picker (below) instead.
|
||||
if !is_paper && !tab.is_start {
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = tab.scene.selection.borrow().view.vp_size;
|
||||
let rect = tab.scene.active_model_tile_bounds(vw, vh);
|
||||
// Unified control chip: split buttons + render-mode picker +
|
||||
// grid / grid-snap toggles, for the active Model tile.
|
||||
@@ -1328,7 +1328,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
let active_vp_rect: Option<(codec::Handle, iced::Rectangle)> =
|
||||
if is_paper && !tab.is_start {
|
||||
tab.scene.active_viewport.and_then(|h| {
|
||||
let (cw, ch) = tab.scene.selection.borrow().vp_size;
|
||||
let (cw, ch) = tab.scene.selection.borrow().view.vp_size;
|
||||
tab.scene
|
||||
.viewport_screen_rect(h, (cw, ch))
|
||||
.map(|rect| (h, rect))
|
||||
@@ -1342,7 +1342,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
// outline inward when the viewport ran off the top/left edge, so
|
||||
// its drawn border no longer matched the real viewport — clicks
|
||||
// that looked outside landed in (and activated) another viewport.
|
||||
let (cw, ch) = tab.scene.selection.borrow().vp_size;
|
||||
let (cw, ch) = tab.scene.selection.borrow().view.vp_size;
|
||||
let x = rect.x.max(0.0);
|
||||
let y = rect.y.max(0.0);
|
||||
let vw = ((rect.x + rect.width).min(cw) - x).max(1.0);
|
||||
@@ -1435,7 +1435,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
// Place the ViewCube hit area in the active model tile's top-right
|
||||
// corner so it tracks the active panel in a tiled layout. The hit
|
||||
// test in update.rs already maps clicks through the active tile.
|
||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||
let (vw, vh) = tab.scene.selection.borrow().view.vp_size;
|
||||
let rect = tab.scene.active_model_tile_bounds(vw, vh);
|
||||
let cube_x = (rect.x + rect.width - VIEWCUBE_HIT_SIZE - VIEWCUBE_PAD).max(0.0);
|
||||
let cube_y = (rect.y + VIEWCUBE_PAD).max(0.0);
|
||||
@@ -1832,7 +1832,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
if !tab.is_start {
|
||||
let (ctx_pos, highlighted) = {
|
||||
let sel = tab.scene.selection.borrow();
|
||||
(sel.context_menu, sel.context_menu_ui.highlighted)
|
||||
(sel.menu.open_at, sel.menu.ui.highlighted)
|
||||
};
|
||||
if let Some(p) = ctx_pos {
|
||||
let menu = self.current_context_menu();
|
||||
@@ -1848,7 +1848,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
// In-place MText editor (toolbar + text area), anchored at the
|
||||
// insertion-point click.
|
||||
if !tab.is_start {
|
||||
let canvas = tab.scene.selection.borrow().vp_size;
|
||||
let canvas = tab.scene.selection.borrow().view.vp_size;
|
||||
if let Some(ed) = &self.mtext_editor {
|
||||
let styles: Vec<String> = tab
|
||||
.scene
|
||||
@@ -2010,7 +2010,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
||||
} else {
|
||||
visible + 1
|
||||
};
|
||||
let edge_h = tab.scene.selection.borrow().vp_size.1;
|
||||
let edge_h = tab.scene.selection.borrow().view.vp_size.1;
|
||||
let slot_h = edge_h / final_count as f32;
|
||||
let index = self.dock_drag_target.map(|(_, i)| i).unwrap_or(0);
|
||||
let slot = index.min(final_count.saturating_sub(1));
|
||||
|
||||
@@ -430,7 +430,7 @@ impl Scene {
|
||||
/// wrong whenever a tiled viewport is active.
|
||||
pub(super) fn active_camera_aspect(&self) -> f32 {
|
||||
if self.current_layout == "Model" {
|
||||
let (canvas_w, canvas_h) = self.selection.borrow().vp_size;
|
||||
let (canvas_w, canvas_h) = self.selection.borrow().view.vp_size;
|
||||
let tiles = self.model_tiles.borrow();
|
||||
let active = self.active_model_tile.get().min(tiles.len().saturating_sub(1));
|
||||
if let Some(tile) = tiles.get(active) {
|
||||
@@ -803,7 +803,7 @@ impl Scene {
|
||||
z: view_dir.z as f64,
|
||||
};
|
||||
entry.view_height = view_height as f64;
|
||||
let (canvas_width, canvas_height) = self.selection.borrow().vp_size;
|
||||
let (canvas_width, canvas_height) = self.selection.borrow().view.vp_size;
|
||||
let viewport_width = (upper_right.x - lower_left.x).abs() * canvas_width as f64;
|
||||
let viewport_height = (upper_right.y - lower_left.y).abs() * canvas_height as f64;
|
||||
if viewport_width > 1e-9 && viewport_height > 1e-9 {
|
||||
@@ -1556,7 +1556,7 @@ impl Scene {
|
||||
return;
|
||||
};
|
||||
|
||||
let (canvas_w, canvas_h) = self.selection.borrow().vp_size;
|
||||
let (canvas_w, canvas_h) = self.selection.borrow().view.vp_size;
|
||||
let fallback = self.last_render_aspect.get().max(0.01);
|
||||
let aspects: Vec<f32> = self
|
||||
.model_tiles
|
||||
|
||||
@@ -3068,7 +3068,7 @@ impl super::Scene {
|
||||
/// `(id, anchor, outward_screen_direction, label, is_conflicting,
|
||||
/// hover_points)` for every enabled, visible constraint whose glyph
|
||||
/// projects on-screen.
|
||||
/// `vp_size` is the full canvas size (as `SelectionState::vp_size`
|
||||
/// `vp_size` is the full canvas size (as `SelectionView::vp_size`
|
||||
/// reports it), matching what `viewport_edit_frame`/
|
||||
/// `active_model_tile_bounds` expect. Mirrors the projection
|
||||
/// `crate::app::view` builds its own render list with. The memoised
|
||||
@@ -3808,7 +3808,7 @@ mod tests {
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
),
|
||||
));
|
||||
scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let id = scene.parametric_constraint_set_mut(scope).add(
|
||||
ConstraintKind::Horizontal,
|
||||
vec![ParametricRef::whole(line)],
|
||||
@@ -3848,7 +3848,7 @@ mod tests {
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
),
|
||||
));
|
||||
scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let id = scene.parametric_constraint_set_mut(scope).add(
|
||||
ConstraintKind::Horizontal,
|
||||
vec![ParametricRef::whole(line)],
|
||||
@@ -3878,7 +3878,7 @@ mod tests {
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
),
|
||||
));
|
||||
scene.selection.borrow_mut().vp_size = (800.0, 600.0);
|
||||
scene.selection.borrow_mut().view.vp_size = (800.0, 600.0);
|
||||
let id = scene.parametric_constraint_set_mut(scope).add(
|
||||
ConstraintKind::Horizontal,
|
||||
vec![ParametricRef::whole(line)],
|
||||
|
||||
@@ -2,9 +2,29 @@ use iced::time::Instant;
|
||||
use iced::Point;
|
||||
|
||||
/// Mouse / selection interaction state for the viewport.
|
||||
///
|
||||
/// Split (#32) into five sub-structs by family so a per-frame clone can copy
|
||||
/// only the family it needs and gesture clears touch one family at a time.
|
||||
/// The `RefCell` around this struct (`Scene::selection`) is untouched.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SelectionState {
|
||||
pub view: SelectionView,
|
||||
pub gesture: SelectionGesture,
|
||||
pub input: SelectionInput,
|
||||
pub menu: SelectionMenu,
|
||||
pub orbit: SelectionOrbit,
|
||||
}
|
||||
|
||||
/// View-dependent snapshot: the canvas size selection geometry is expressed in.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SelectionView {
|
||||
pub vp_size: (f32, f32),
|
||||
}
|
||||
|
||||
/// One box-or-lasso gesture: anchor/current corners, crossing sense, and the
|
||||
/// in-progress polygon. Cleared together by [`SelectionGesture::clear_left`].
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SelectionGesture {
|
||||
pub box_anchor: Option<Point>,
|
||||
/// World point under the box-selection anchor, so the anchor can be
|
||||
/// re-projected to screen when the camera zooms/pans mid-drag instead of
|
||||
@@ -12,14 +32,12 @@ pub struct SelectionState {
|
||||
/// (#234)
|
||||
pub box_anchor_world: Option<glam::DVec3>,
|
||||
pub box_current: Option<Point>,
|
||||
pub box_last: Option<(Point, Point)>,
|
||||
pub box_crossing: bool,
|
||||
/// Set when a Window / Crossing selection keyword fixed the sense of the
|
||||
/// box being drawn. Dragging normally decides it from the direction the
|
||||
/// corner travels, which would immediately overwrite what the user just
|
||||
/// asked for, so that derivation stands down while this holds.
|
||||
pub box_crossing_locked: bool,
|
||||
pub box_last_crossing: bool,
|
||||
/// A preview-only selection marquee `(anchor, current, crossing)` in canvas
|
||||
/// pixels, drawn identically to a real box-selection (green crossing fill /
|
||||
/// blue window fill) but with NO hit-test behaviour. Commands that pick a
|
||||
@@ -29,14 +47,25 @@ pub struct SelectionState {
|
||||
pub poly_active: bool,
|
||||
pub poly_points: Vec<Point>,
|
||||
pub poly_crossing: bool,
|
||||
pub poly_last_crossing: bool,
|
||||
/// Canvas position the right-click context menu is anchored at while it
|
||||
/// is open. `None` = closed. Its rows are rebuilt from app state every
|
||||
/// frame (`ui::popup::context_menu::build_context_menu`).
|
||||
pub context_menu: Option<Point>,
|
||||
/// Transient UI state of the open context menu (expanded submenu,
|
||||
/// keyboard highlight). Reset every time the menu opens.
|
||||
pub context_menu_ui: ContextMenuUi,
|
||||
}
|
||||
|
||||
impl SelectionGesture {
|
||||
/// End the in-progress box/lasso gesture (left-button release path).
|
||||
pub fn clear_left(&mut self) {
|
||||
self.box_anchor = None;
|
||||
self.box_anchor_world = None;
|
||||
self.box_current = None;
|
||||
self.box_crossing = false;
|
||||
self.box_crossing_locked = false;
|
||||
self.poly_active = false;
|
||||
self.poly_points.clear();
|
||||
self.poly_crossing = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw button/pointer state: press positions, drag flags, orbit pivot input.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SelectionInput {
|
||||
pub last_move_pos: Option<Point>,
|
||||
pub left_down: bool,
|
||||
pub left_press_pos: Option<Point>,
|
||||
@@ -47,10 +76,6 @@ pub struct SelectionState {
|
||||
pub right_press_time: Option<Instant>,
|
||||
pub right_dragging: bool,
|
||||
pub right_last_pos: Option<Point>,
|
||||
/// World point the current orbit drag revolves around (selection or model
|
||||
/// centre), captured when the drag starts so it stays fixed for the whole
|
||||
/// gesture. `None` when no orbit is in progress. (#229)
|
||||
pub orbit_pivot: Option<glam::DVec3>,
|
||||
/// While a command is active, a right-click acts as Enter; the *next*
|
||||
/// consecutive right-click opens the context menu instead. This tracks
|
||||
/// whether the previous right-click already fired Enter. Reset by any
|
||||
@@ -66,6 +91,52 @@ pub struct SelectionState {
|
||||
pub zoom_dir_out: bool,
|
||||
}
|
||||
|
||||
impl SelectionInput {
|
||||
/// Release the left button (click-commit path).
|
||||
pub fn clear_left_buttons(&mut self) {
|
||||
self.left_down = false;
|
||||
self.left_press_pos = None;
|
||||
self.left_press_time = None;
|
||||
self.left_dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Right-click context-menu state.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SelectionMenu {
|
||||
/// Canvas position the right-click context menu is anchored at while it
|
||||
/// is open. `None` = closed. Its rows are rebuilt from app state every
|
||||
/// frame (`ui::popup::context_menu::build_context_menu`).
|
||||
pub open_at: Option<Point>,
|
||||
/// Transient UI state of the open context menu (expanded submenu,
|
||||
/// keyboard highlight). Reset every time the menu opens.
|
||||
pub ui: ContextMenuUi,
|
||||
}
|
||||
|
||||
impl SelectionMenu {
|
||||
/// Open the context menu at `at`, discarding the previous menu's
|
||||
/// expanded-submenu / highlight state.
|
||||
pub fn open(&mut self, at: Point) {
|
||||
self.open_at = Some(at);
|
||||
self.ui = ContextMenuUi::default();
|
||||
}
|
||||
|
||||
/// Close the context menu (no-op when it is not open).
|
||||
pub fn close(&mut self) {
|
||||
self.open_at = None;
|
||||
self.ui = ContextMenuUi::default();
|
||||
}
|
||||
}
|
||||
|
||||
/// Orbit-drag state.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct SelectionOrbit {
|
||||
/// World point the current orbit drag revolves around (selection or model
|
||||
/// centre), captured when the drag starts so it stays fixed for the whole
|
||||
/// gesture. `None` when no orbit is in progress. (#229)
|
||||
pub pivot: Option<glam::DVec3>,
|
||||
}
|
||||
|
||||
/// Transient state of the open right-click context menu.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct ContextMenuUi {
|
||||
@@ -78,36 +149,12 @@ pub struct ContextMenuUi {
|
||||
}
|
||||
|
||||
impl SelectionState {
|
||||
/// Open the context menu at `at`, discarding the previous menu's
|
||||
/// expanded-submenu / highlight state.
|
||||
pub fn open_context_menu(&mut self, at: Point) {
|
||||
self.context_menu = Some(at);
|
||||
self.context_menu_ui = ContextMenuUi::default();
|
||||
}
|
||||
|
||||
/// Close the context menu (no-op when it is not open).
|
||||
pub fn close_context_menu(&mut self) {
|
||||
self.context_menu = None;
|
||||
self.context_menu_ui = ContextMenuUi::default();
|
||||
}
|
||||
|
||||
/// End every left-button selection gesture without disturbing the previous
|
||||
/// completed-window record or command-owned preview marquee. Grip editing
|
||||
/// owns the left button while engaged and calls this before/after placement
|
||||
/// so a small pointer move cannot also arm a box or lasso selection.
|
||||
pub fn clear_left_selection_gesture(&mut self) {
|
||||
self.left_down = false;
|
||||
self.left_press_pos = None;
|
||||
self.left_press_time = None;
|
||||
self.left_dragging = false;
|
||||
self.box_anchor = None;
|
||||
self.box_anchor_world = None;
|
||||
self.box_current = None;
|
||||
self.box_crossing = false;
|
||||
self.box_crossing_locked = false;
|
||||
self.poly_active = false;
|
||||
self.poly_points.clear();
|
||||
self.poly_crossing = false;
|
||||
self.input.clear_left_buttons();
|
||||
self.gesture.clear_left();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +281,7 @@ impl Scene {
|
||||
self.interim_wire = None;
|
||||
// Drop any point-picked window marquee (STRETCH) so it doesn't linger
|
||||
// after the command ends before the next mouse move. (#291)
|
||||
self.selection.borrow_mut().preview_box = None;
|
||||
self.selection.borrow_mut().gesture.preview_box = None;
|
||||
}
|
||||
|
||||
pub fn wire_models_for(&self, handles: &[codec::Handle]) -> Vec<WireModel> {
|
||||
|
||||
@@ -4213,7 +4213,7 @@ impl Scene {
|
||||
// `CursorMoved` handler — the cube overlay sits above the shader
|
||||
// and would otherwise mask the move event from `Program::update`.
|
||||
let hover_region = show_interaction.then(|| self.viewcube_hover.get()).flatten();
|
||||
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
|
||||
self.selection.borrow_mut().view.vp_size = (bounds.width, bounds.height);
|
||||
if bounds.height > 0.0 {
|
||||
self.set_render_aspect(bounds.width / bounds.height);
|
||||
self.set_render_pixel_scale(bounds.width, bounds.height);
|
||||
|
||||
+17
-17
@@ -1222,7 +1222,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
// under way. Hovering before the drag starts shows the neutral "+"
|
||||
// magnifier rather than guessing a direction.
|
||||
if self.nav != NavCursor::None && cursor.is_over(bounds) {
|
||||
let dragging = self.selection.borrow().middle_down;
|
||||
let dragging = self.selection.borrow().input.middle_down;
|
||||
return match self.nav {
|
||||
NavCursor::Pan => {
|
||||
if dragging {
|
||||
@@ -1233,7 +1233,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
}
|
||||
NavCursor::Orbit => mouse::Interaction::AllScroll,
|
||||
NavCursor::Zoom => {
|
||||
if dragging && self.selection.borrow().zoom_dir_out {
|
||||
if dragging && self.selection.borrow().input.zoom_dir_out {
|
||||
mouse::Interaction::ZoomOut
|
||||
} else {
|
||||
mouse::Interaction::ZoomIn
|
||||
@@ -1338,7 +1338,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
}
|
||||
// Ghost card dragged under the cursor — a 0.32× preview of the
|
||||
// source pane, centred on the cursor.
|
||||
if let Some(c) = self.selection.borrow().last_move_pos {
|
||||
if let Some(c) = self.selection.borrow().input.last_move_pos {
|
||||
let gw = (src.width * 0.32).clamp(60.0, 280.0);
|
||||
let gh = (src.height * 0.32).clamp(40.0, 200.0);
|
||||
let g = canvas::Path::rectangle(
|
||||
@@ -1408,27 +1408,27 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
// the view zooms. Suppress the drawing, not the state — the anchor is
|
||||
// still re-projected as the camera moves.
|
||||
if self.nav != NavCursor::Zoom {
|
||||
if let (Some(a), Some(b)) = (self.selection.borrow().box_anchor, self.selection.borrow().box_current) {
|
||||
draw_marquee(&mut frame, a, b, self.selection.borrow().box_crossing, theme, &self.selection_visual, self.crosshair_bg);
|
||||
if let (Some(a), Some(b)) = (self.selection.borrow().gesture.box_anchor, self.selection.borrow().gesture.box_current) {
|
||||
draw_marquee(&mut frame, a, b, self.selection.borrow().gesture.box_crossing, theme, &self.selection_visual, self.crosshair_bg);
|
||||
}
|
||||
}
|
||||
// Preview marquee for point-picked windows (STRETCH) — same look, no pick.
|
||||
if let Some((a, b, crossing)) = self.selection.borrow().preview_box {
|
||||
if let Some((a, b, crossing)) = self.selection.borrow().gesture.preview_box {
|
||||
draw_marquee(&mut frame, a, b, crossing, theme, &self.selection_visual, self.crosshair_bg);
|
||||
}
|
||||
|
||||
if self.selection.borrow().poly_active && self.selection.borrow().poly_points.len() > 1 {
|
||||
let crossing = self.selection.borrow().poly_crossing;
|
||||
if self.selection.borrow().gesture.poly_active && self.selection.borrow().gesture.poly_points.len() > 1 {
|
||||
let crossing = self.selection.borrow().gesture.poly_crossing;
|
||||
let base = resolve_selection_base_color(crossing, theme, &self.selection_visual, self.crosshair_bg);
|
||||
let canvas_light = crate::ui::style::common::canvas_is_light(self.crosshair_bg);
|
||||
if self.selection_visual.area && self.selection_visual.opacity > 0 {
|
||||
let alpha = selection_fill_alpha(self.selection_visual.opacity as f32, canvas_light);
|
||||
let fill = base.scale_alpha(alpha);
|
||||
if let Some(cur) = self.selection.borrow().last_move_pos {
|
||||
let start = self.selection.borrow().poly_points[0];
|
||||
if let Some(cur) = self.selection.borrow().input.last_move_pos {
|
||||
let start = self.selection.borrow().gesture.poly_points[0];
|
||||
let fill_path = canvas::Path::new(|p| {
|
||||
p.move_to(start);
|
||||
for pt in &self.selection.borrow().poly_points[1..] {
|
||||
for pt in &self.selection.borrow().gesture.poly_points[1..] {
|
||||
p.line_to(*pt);
|
||||
}
|
||||
p.line_to(cur);
|
||||
@@ -1440,8 +1440,8 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
let stroke_alpha = if canvas_light { 0.95 } else { 0.90 };
|
||||
let stroke = base.scale_alpha(stroke_alpha);
|
||||
let path = canvas::Path::new(|p| {
|
||||
p.move_to(self.selection.borrow().poly_points[0]);
|
||||
for pt in &self.selection.borrow().poly_points[1..] {
|
||||
p.move_to(self.selection.borrow().gesture.poly_points[0]);
|
||||
for pt in &self.selection.borrow().gesture.poly_points[1..] {
|
||||
p.line_to(*pt);
|
||||
}
|
||||
});
|
||||
@@ -1459,9 +1459,9 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
..Default::default()
|
||||
};
|
||||
frame.stroke(&path, stroke_style.clone());
|
||||
if let Some(cur) = self.selection.borrow().last_move_pos {
|
||||
let start = self.selection.borrow().poly_points[0];
|
||||
let last = *self.selection.borrow().poly_points.last().unwrap();
|
||||
if let Some(cur) = self.selection.borrow().input.last_move_pos {
|
||||
let start = self.selection.borrow().gesture.poly_points[0];
|
||||
let last = *self.selection.borrow().gesture.poly_points.last().unwrap();
|
||||
let preview = canvas::Path::new(|p| {
|
||||
p.move_to(last);
|
||||
p.line_to(cur);
|
||||
@@ -1898,7 +1898,7 @@ impl canvas::Program<Message> for SelectionCanvas {
|
||||
&& !self.suppressed
|
||||
&& self.crosshair.cursor_type == CursorType::Crosshair
|
||||
{
|
||||
if let Some(cp) = self.selection.borrow().last_move_pos {
|
||||
if let Some(cp) = self.selection.borrow().input.last_move_pos {
|
||||
let [r, g, b, a] = self.crosshair.color.map_or_else(
|
||||
|| {
|
||||
crate::scene::view::render::adapt_to_bg(
|
||||
|
||||
@@ -34,7 +34,7 @@ pub const MENU_WIDTH_WITH_HINTS: f32 = 236.0;
|
||||
pub const RECENT_LIMIT: usize = 10;
|
||||
|
||||
/// Identifies an inline-expanding (accordion) submenu. Only one is open at a
|
||||
/// time; the open one is tracked in `SelectionState::context_menu_ui`.
|
||||
/// time; the open one is tracked in `SelectionMenu::ui`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SubmenuId {
|
||||
RecentInput,
|
||||
|
||||
Reference in new issue
Block a user