Skip to main content

geop_core_editor/tools/
pick.rs

1//! The pick tool: an inspector that picks (see
2//! [`crate::output::InspectorInfo::picks`]) in hand, which picks entities
3//! in the viewport, and is asked about them again with every pick.
4//!
5//! It is no operation: inspecting changes no program, adds no step and is
6//! nothing to undo. What the picks are asked is the inspector's.
7
8use geop_core_math::geop_error::{GeopError, GeopResult};
9use geop_core_program::element::PickedElement;
10
11use super::Tool;
12use crate::{
13    editing::{
14        Dialog, Layout, Presentation, StepEditEvent,
15        hit::{Cycle, Scene, cycled},
16    },
17    editor::{Changed, EditorContext},
18    output::{Inspection, InspectorInfo, Picks},
19};
20
21/// An inspector that picks, in hand: what the pointer is over, what is
22/// picked, and the inspector's answer about it.
23pub struct PickTool {
24    info: &'static InspectorInfo,
25    picks: Picks,
26    hover: Option<PickedElement>,
27    /// Which of what lies under the pointer the hover is (see
28    /// [`crate::editing::Pointer::cycle`]).
29    cycle: Option<Cycle>,
30    picked: Vec<PickedElement>,
31    /// The answer about the picks, once asked, with the run of the part it
32    /// was asked of: none while it is to be asked again.
33    answer: Option<(u64, Inspection)>,
34}
35
36impl PickTool {
37    /// What the key of an inspector's pick tool starts with:
38    /// `pick:measure`.
39    pub const PREFIX: &'static str = "pick:";
40
41    /// The tool of the inspector `info`. Fails for one that is asked of the
42    /// whole part.
43    pub fn new(info: &'static InspectorInfo) -> GeopResult<Self> {
44        let picks = info.picks.ok_or_else(|| {
45            GeopError::new(format!(
46                "the inspector `{}` picks nothing: it is asked of the whole part",
47                info.kind
48            ))
49        })?;
50        Ok(Self {
51            info,
52            picks,
53            hover: None,
54            cycle: None,
55            picked: Vec::new(),
56            answer: None,
57        })
58    }
59
60    /// Its inspector's kind.
61    pub fn kind(&self) -> &'static str {
62        self.info.kind
63    }
64
65    /// What is picked, the oldest first.
66    pub fn picked(&self) -> &[PickedElement] {
67        &self.picked
68    }
69
70    /// The kinds of what is drawn it picks from.
71    fn accepts(&self) -> Vec<String> {
72        self.picks
73            .accepts
74            .iter()
75            .map(|kind| kind.to_string())
76            .collect()
77    }
78}
79
80impl Tool for PickTool {
81    /// An event in the viewport, picking in what is drawn: a hover finds
82    /// what a click would pick; a click picks it — joining the picks, or
83    /// replacing the oldest once there are as many as the inspector takes;
84    /// a click on one picked takes it out, a click on nothing clears them;
85    /// Escape clears them too. A change of the picks asks again.
86    fn handle(&mut self, ctx: &mut EditorContext, event: &StepEditEvent) -> GeopResult<Changed> {
87        let Some(view) = ctx.drawn() else {
88            return Ok(Changed::Nothing);
89        };
90        let accepts = self.accepts();
91        let picked = |pointer| view.pick(pointer, &accepts).map(|hit| hit.element);
92        let before = self.picked.clone();
93        match event {
94            StepEditEvent::Hover { pointer, .. } => {
95                let under = view.under(pointer, &accepts);
96                self.cycle = Cycle::of(under.len(), pointer);
97                self.hover = cycled(under, pointer.cycle).map(|hit| hit.element);
98            }
99            StepEditEvent::Leave => {
100                self.hover = None;
101                self.cycle = None;
102            }
103            StepEditEvent::Click { pointer, .. } => match picked(pointer) {
104                Some(entity) => match self.picked.iter().position(|p| *p == entity) {
105                    Some(i) => {
106                        self.picked.remove(i);
107                    }
108                    None => {
109                        self.picked.push(entity);
110                        if self.picked.len() > self.picks.most {
111                            self.picked.remove(0);
112                        }
113                    }
114                },
115                None => self.picked.clear(),
116            },
117            StepEditEvent::Key { key } if key == "Escape" => self.picked.clear(),
118            _ => {}
119        }
120        if self.picked != before {
121            self.answer = None;
122        }
123        Ok(Changed::Nothing)
124    }
125
126    /// What it shows: the picks and what a click would pick lit, and what
127    /// the answer draws.
128    fn presentation(&self) -> Presentation {
129        let mut highlights = self.picked.clone();
130        highlights.extend(
131            self.hover
132                .iter()
133                .filter(|h| !self.picked.contains(h))
134                .cloned(),
135        );
136        Presentation {
137            dialog: Dialog::new(),
138            layout: Layout::default(),
139            visuals: (self.answer.iter())
140                .flat_map(|(_, answer)| answer.visuals.iter().cloned())
141                .collect(),
142            highlights,
143            pickable: self.accepts(),
144            focus: None,
145            sheet: None,
146            grab: false,
147            select_box: false,
148            prompt: None,
149            gizmo: None,
150            hidden: Vec::new(),
151            cycle: self.cycle,
152        }
153    }
154
155    /// The inspector's answer about the picks, asked again only if they or
156    /// the part changed since.
157    fn inspection(&mut self, ctx: &mut EditorContext) -> GeopResult<Option<Inspection>> {
158        let runs = ctx.runs();
159        if let Some((asked, answer)) = &self.answer
160            && *asked == runs
161        {
162            return Ok(Some(answer.clone()));
163        }
164        let answer = ctx.inspect(self.info.kind, &self.picked)?;
165        self.answer = Some((runs, answer.clone()));
166        Ok(Some(answer))
167    }
168}