1use geop_core_editor::{
5 editing::{Dialog, Layout, Presentation, Pull, Shape, StepEditEvent, Style, Visual},
6 editor::{Changed, EditorContext},
7 tools::{Tool, ToolInfo},
8};
9use geop_core_math::{
10 geop_error::GeopResult, primitives::Pose, solvers::system::Structure, vector::Vector3,
11};
12use geop_core_program::program::Program;
13use geop_ops_model::placed::locate;
14
15use crate::{
16 icons,
17 solving::{self, Report},
18};
19
20#[derive(Clone, Default)]
23pub struct DragTool {
24 hover: Option<String>,
25 grab: Option<Grabbed>,
26 dragged: Option<Report>,
28}
29
30#[derive(Clone)]
32struct Grabbed {
33 name: String,
35 parameter: String,
37 local: Vector3,
40 plane: (Vector3, Vector3),
43 drawn_off: Option<Pose>,
46 before: Program,
48 structure: Option<Structure>,
51}
52
53impl DragTool {
54 pub const INFO: ToolInfo = ToolInfo {
56 key: "drag",
57 label: "Drag",
58 doc: "Drag placed parts as far as their mates let them",
59 icon: Some(icons::DRAG),
60 };
61
62 pub fn dragged(&self) -> Option<&Report> {
65 self.dragged.as_ref()
66 }
67}
68
69impl Tool for DragTool {
70 fn handle(&mut self, ctx: &mut EditorContext, event: &StepEditEvent) -> GeopResult<Changed> {
76 let part_at = |ctx: &EditorContext, pointer| {
80 let view = ctx.drawn()?;
81 let (instance, t) = view.part_to_drag(pointer)?;
82 let parameter = instance.state.clone()?;
83 let assembled = locate(&ctx.part(), &instance.name).ok()?.world;
84 let drawn_off =
85 (instance.pose != assembled).then(|| instance.pose.compose(&assembled.inverse()));
86 Some((
87 instance.name.clone(),
88 parameter,
89 instance.pose,
90 t,
91 drawn_off,
92 ))
93 };
94 Ok(match event {
95 StepEditEvent::Hover { pointer, .. } => {
96 self.hover = part_at(ctx, pointer).map(|(name, ..)| name);
97 Changed::Nothing
98 }
99 StepEditEvent::Leave => {
100 self.hover = None;
101 Changed::Nothing
102 }
103 StepEditEvent::Drag { from, to, done, .. } => {
104 if self.grab.is_none()
105 && let Some((name, parameter, pose, t, drawn_off)) = part_at(ctx, from)
106 {
107 let at = from.ray.at(t);
108 self.grab = Some(Grabbed {
109 name,
110 parameter,
111 local: pose.inverse().apply(&at).sharpen(),
112 plane: (at, *from.ray.dir()),
113 drawn_off,
114 before: ctx.program().clone(),
115 structure: None,
116 });
117 }
118 let Some(grab) = &self.grab else {
119 return Ok(Changed::Nothing);
120 };
121 let (point, normal) = &grab.plane;
122 if let Some((_, target)) = to.ray.intersect_plane(point, normal) {
123 ctx.run_all();
124 let target = match &grab.drawn_off {
125 Some(off) => off.inverse().apply(&target),
126 None => target,
127 };
128 let pull = Pull::Point {
129 parameter: grab.parameter.clone(),
130 local: grab.local,
131 target: target.sharpen(),
132 };
133 let known = grab.structure.clone();
134 let mut rerun = ctx.rerun();
135 let state = rerun.program.state.clone();
136 let solved =
137 solving::solve(&mut rerun, &state, &[pull], None, *done, known.as_ref());
138 if let Ok((moved, report)) = solved {
139 ctx.move_state(moved);
140 if let Some(grab) = &mut self.grab {
141 grab.structure = Some(report.structure.clone());
142 }
143 self.dragged = Some(report);
144 }
145 }
146 if *done && let Some(grab) = self.grab.take() {
147 ctx.undoable(grab.before);
148 }
149 match done {
150 true => Changed::Run,
151 false => Changed::State,
152 }
153 }
154 _ => Changed::Nothing,
155 })
156 }
157
158 fn presentation(&self) -> Presentation {
161 let lit = (self.grab.as_ref().map(|g| &g.name)).or(self.hover.as_ref());
162 Presentation {
163 dialog: Dialog::new(),
164 layout: Layout::default(),
165 visuals: (lit.map(|name| {
166 let shape = Shape::Instance { name: name.clone() };
167 Visual::new("drag", shape, Style::Hover)
168 }))
169 .into_iter()
170 .collect(),
171 highlights: Vec::new(),
172 pickable: Vec::new(),
173 focus: None,
174 sheet: None,
175 grab: lit.is_some(),
176 select_box: false,
177 prompt: None,
178 gizmo: None,
179 hidden: Vec::new(),
180 cycle: None,
181 }
182 }
183}