Skip to main content

geop_ops_assembly/
drag_tool.rs

1//! The drag tool ([`DragTool`]): in hand while no step is edited, it drags
2//! any placed part as far as the program's mates let it.
3
4use 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/// The drag tool, in hand: the placed part the pointer is over, and the one
21/// being dragged.
22#[derive(Clone, Default)]
23pub struct DragTool {
24    hover: Option<String>,
25    grab: Option<Grabbed>,
26    /// How the solve of its last drag went.
27    dragged: Option<Report>,
28}
29
30/// A placed part being dragged by the drag tool.
31#[derive(Clone)]
32struct Grabbed {
33    /// Its name, as drawn.
34    name: String,
35    /// The state its pose is.
36    parameter: String,
37    /// The point grabbed, in its own frame: where the pointer was, a free
38    /// choice, so sharp.
39    local: Vector3,
40    /// The plane it is dragged in: through the point grabbed, facing the
41    /// eye — `(point, normal)`.
42    plane: (Vector3, Vector3),
43    /// How far it is drawn off where it is assembled, by the moves of an
44    /// exploded view: none where it is drawn as assembled.
45    drawn_off: Option<Pose>,
46    /// The program when it was grabbed: what undoing the drag goes back to.
47    before: Program,
48    /// The groups the last event of the drag was solved in: the next is
49    /// solved in them too, a hair away (see [`solving::solve`]).
50    structure: Option<Structure>,
51}
52
53impl DragTool {
54    /// How an editor offers it, and its key.
55    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    /// How the solve of its last drag went: whether the constraints hold,
63    /// and how hard it was to get there.
64    pub fn dragged(&self) -> Option<&Report> {
65        self.dragged.as_ref()
66    }
67}
68
69impl Tool for DragTool {
70    /// An event in the viewport: a hover finds the placed part a press
71    /// would grab, a drag pulls the point grabbed towards the pointer — in
72    /// the plane through it facing the eye — and solves the program's mates
73    /// with that pull, every part that is not fixed free to give way. A
74    /// drag, once released, is one edit to undo.
75    fn handle(&mut self, ctx: &mut EditorContext, event: &StepEditEvent) -> GeopResult<Changed> {
76        // A part is grabbed where it is drawn; an exploded view draws it
77        // moved off where it is assembled, and what is dragged is where it
78        // is assembled: the grab is taken back by that move.
79        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    /// What it shows: the part it would drag, or drags, lit — and that a
159    /// press grabs it.
160    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}