geop_core_editor/tools/
pick.rs1use 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
21pub struct PickTool {
24 info: &'static InspectorInfo,
25 picks: Picks,
26 hover: Option<PickedElement>,
27 cycle: Option<Cycle>,
30 picked: Vec<PickedElement>,
31 answer: Option<(u64, Inspection)>,
34}
35
36impl PickTool {
37 pub const PREFIX: &'static str = "pick:";
40
41 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 pub fn kind(&self) -> &'static str {
62 self.info.kind
63 }
64
65 pub fn picked(&self) -> &[PickedElement] {
67 &self.picked
68 }
69
70 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 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 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 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}