geop_core_editor/editing/event.rs
1//! [`StepEditEvent`]: what the user did, as an operation receives it.
2
3use geop_core_math::{
4 geop_error::GeopResult,
5 primitives::{Motion, Ray},
6 scalars::{Scalar, as_f64},
7 vector::Vector3,
8};
9use serde::{Deserialize, Serialize};
10
11use super::{Gizmo, GizmoDrag};
12use geop_core_program::{element::PickedElement, numeric::NumericInput};
13
14/// Dragged values snap to this: a drag is for rough shaping, a dialog for
15/// exact values.
16pub const DRAG_SNAP: f64 = 0.01;
17
18/// How far from its ray a [`Pointer`] reaches: what counts as under it, at
19/// any distance along the ray. Everything a viewer draws at a constant size
20/// on screen — a handle, a label, a frame datum — is laid out in reaches,
21/// so it is hit where it is drawn however far away or zoomed in the view is.
22#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
23#[serde(tag = "type", rename_all = "snake_case")]
24pub enum Reach {
25 /// Seen in perspective: a cone around the ray, from its origin — the
26 /// eye — widening by `slope` per unit of distance.
27 Cone {
28 #[serde(with = "as_f64")]
29 slope: Scalar,
30 },
31 /// Seen orthographically: a tube of `radius` around the ray.
32 Tube {
33 #[serde(with = "as_f64")]
34 radius: Scalar,
35 },
36}
37
38impl Reach {
39 /// How far from the ray it reaches, `t` along it.
40 pub fn at(&self, t: Scalar) -> Scalar {
41 match *self {
42 Reach::Cone { slope } => slope.mul(t),
43 Reach::Tube { radius } => radius,
44 }
45 }
46}
47
48/// Where the pointer is: the ray from the eye through it, how far from
49/// that ray it reaches, and which of what lies under it it means.
50#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
51pub struct Pointer {
52 pub ray: Ray,
53 pub reach: Reach,
54 /// Which of what lies under the pointer it means, counted front to
55 /// back from the one a click picks first (see
56 /// [`super::hit::Scene::under`]), wrapping round: a secondary click
57 /// steps one further behind, and any move of the pointer starts again
58 /// from the front. A pick, a hover and a click alike take the one
59 /// this names, so what is lit is what a click holds.
60 #[serde(default)]
61 pub cycle: usize,
62}
63
64impl Pointer {
65 /// The pointer moved by `motion`: into the frame of a part placed in
66 /// the part drawn, for a step of it to be edited where it is placed. A
67 /// rigid motion leaves its reach as it is.
68 pub fn moved(&self, motion: &Motion) -> GeopResult<Self> {
69 let ray = Ray::try_new(
70 motion.apply(self.ray.origin()),
71 motion.rotate(self.ray.dir()),
72 )?;
73 Ok(Pointer { ray, ..*self })
74 }
75
76 /// The same pointer, meaning the `cycle`-th of what lies under it (see
77 /// [`Pointer::cycle`]).
78 pub fn cycled(self, cycle: usize) -> Self {
79 Pointer { cycle, ..self }
80 }
81
82 /// `reaches` of the pointer's reach, `t` along its ray.
83 pub fn reach_at(&self, reaches: f64, t: Scalar) -> Scalar {
84 self.reach.at(t).mul(Scalar::from_f64(reaches))
85 }
86
87 /// Whether something `dist` from the ray, `t` along it, could lie within
88 /// `reaches` of it.
89 pub fn within(&self, dist: Scalar, t: Scalar, reaches: f64) -> bool {
90 !dist.definitely_greater(self.reach_at(reaches, t))
91 }
92}
93
94/// What a field was set to — or that it was pressed: in the dialog, by a
95/// pick in the viewport, or by dragging a handle.
96#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
97#[serde(tag = "type", content = "value", rename_all = "snake_case")]
98pub enum Value {
99 /// A button, a list item or a reference field was pressed.
100 Press,
101 /// A list item's remove button was pressed.
102 Remove,
103 Bool(bool),
104 Number(f64),
105 /// Text typed: a value given as a formula, a colour.
106 Text(String),
107 /// An action, or a select's option, by its value.
108 Choice(String),
109 /// The element at this index was taken out of a reference field.
110 RemoveAt(usize),
111 /// Everything was taken out of a reference field.
112 Clear,
113 /// What a reference field holds now: all an operation is ever sent for
114 /// one (see [`super::Reference`]).
115 Elements(Vec<PickedElement>),
116 /// Something done to a table field (see [`super::Table`]).
117 Table(TableEdit),
118}
119
120/// What is done to a [`super::Table`], one thing at a time; rows and
121/// columns are named by their index in the table as it was shown.
122#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
123#[serde(tag = "edit", rename_all = "snake_case")]
124pub enum TableEdit {
125 /// The cell at `row`, `column` typed into.
126 Cell {
127 row: usize,
128 column: usize,
129 text: String,
130 },
131 /// A block of text pasted at `row`, `column` — from a spreadsheet, a
132 /// row of cells per line — filling the cells from there on, and adding
133 /// rows below the last for what runs over. An operation never sees
134 /// this: its form takes it apart into the rows added and the cells set
135 /// (see [`crate::edit_form::EditForm::table`]).
136 Paste {
137 row: usize,
138 column: usize,
139 cells: Vec<Vec<String>>,
140 },
141 /// A row added, after the last.
142 AddRow,
143 RemoveRow {
144 row: usize,
145 },
146 /// The row `row` moved to be the row `to`.
147 MoveRow {
148 row: usize,
149 to: usize,
150 },
151 /// The row chosen, of a table one row of which is.
152 SelectRow {
153 row: usize,
154 },
155 /// The header of the column `column` typed into.
156 Column {
157 column: usize,
158 text: String,
159 },
160 /// A column added, after the last.
161 AddColumn,
162 RemoveColumn {
163 column: usize,
164 },
165}
166
167impl Value {
168 /// The [`NumericInput`] a number field set to it gives: a number slid
169 /// or dragged plain, text typed plain if it is a number and a formula
170 /// otherwise. `None` for anything else, and for empty text. A formula
171 /// that does not evaluate is kept, as typed: the step then fails naming
172 /// it, and the field shows the text to correct.
173 pub fn numeric_input(&self) -> Option<NumericInput> {
174 match self {
175 Value::Number(v) => Some(NumericInput::Plain(*v)),
176 Value::Text(text) if !text.trim().is_empty() => Some(NumericInput::from(text.as_str())),
177 _ => None,
178 }
179 }
180}
181
182/// One thing the user did while editing a step.
183#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
184#[serde(tag = "type", rename_all = "snake_case")]
185pub enum StepEditEvent {
186 /// The dialog field `key` was used.
187 Dialog { key: String, value: Value },
188 /// The pointer moved over the viewport with no button held — `shift`
189 /// held or not, which turns snapping off.
190 Hover {
191 pointer: Pointer,
192 #[serde(default)]
193 shift: bool,
194 },
195 /// The pointer left the viewport.
196 Leave,
197 /// A click in the viewport: a press and release of the primary button
198 /// without moving. (The secondary button's click is no event of its
199 /// own: it steps the pointer's [`Pointer::cycle`].)
200 Click {
201 pointer: Pointer,
202 /// The second click of a double click.
203 #[serde(default)]
204 double: bool,
205 #[serde(default)]
206 shift: bool,
207 },
208 /// A drag with the primary button that started where the last
209 /// presentation offered a grab (see [`super::Presentation::grab`]): the
210 /// ray where it went down and the ray where the pointer is now. `done`
211 /// on release.
212 Drag {
213 from: Pointer,
214 to: Pointer,
215 #[serde(default)]
216 done: bool,
217 #[serde(default)]
218 shift: bool,
219 },
220 /// A box drawn by a drag with the primary button from where the last
221 /// presentation offered no grab, while it offered a box (see
222 /// [`super::Presentation::select_box`]): the rays through its corners,
223 /// in order round it, released. `crossing` — drawn right to left — it
224 /// selects what it touches, else what lies wholly in it; `add` — Shift
225 /// or Ctrl held — it adds to the selection rather than replacing it.
226 SelectBox {
227 corners: Box<[Pointer; 4]>,
228 #[serde(default)]
229 crossing: bool,
230 #[serde(default)]
231 add: bool,
232 },
233 /// A key, as the browser names it: `l`, `Escape`, `Enter`, `Delete`.
234 Key { key: String },
235 /// The view settled: `up` is the direction in the world that points up
236 /// the screen. Sent when the camera comes to rest and when the pointer
237 /// enters the viewport, so a step can name what it shows as it is seen
238 /// (a sketch's horizontal is the screen's).
239 View { up: Vector3 },
240}
241
242impl StepEditEvent {
243 /// The event with its pointers moved by `motion` (see
244 /// [`Pointer::moved`]); one without a pointer, as it is.
245 pub fn moved(&self, motion: &Motion) -> GeopResult<Self> {
246 Ok(match self {
247 StepEditEvent::Hover { pointer, shift } => StepEditEvent::Hover {
248 pointer: pointer.moved(motion)?,
249 shift: *shift,
250 },
251 StepEditEvent::Click {
252 pointer,
253 double,
254 shift,
255 } => StepEditEvent::Click {
256 pointer: pointer.moved(motion)?,
257 double: *double,
258 shift: *shift,
259 },
260 StepEditEvent::Drag {
261 from,
262 to,
263 done,
264 shift,
265 } => StepEditEvent::Drag {
266 from: from.moved(motion)?,
267 to: to.moved(motion)?,
268 done: *done,
269 shift: *shift,
270 },
271 StepEditEvent::View { up } => StepEditEvent::View {
272 up: motion.rotate(up),
273 },
274 StepEditEvent::SelectBox {
275 corners,
276 crossing,
277 add,
278 } => {
279 let [a, b, c, d] = &**corners;
280 StepEditEvent::SelectBox {
281 corners: Box::new([
282 a.moved(motion)?,
283 b.moved(motion)?,
284 c.moved(motion)?,
285 d.moved(motion)?,
286 ]),
287 crossing: *crossing,
288 add: *add,
289 }
290 }
291 other => other.clone(),
292 })
293 }
294
295 /// The dialog field this event uses, and its value.
296 pub fn dialog(&self) -> Option<(&str, &Value)> {
297 match self {
298 StepEditEvent::Dialog { key, value } => Some((key, value)),
299 _ => None,
300 }
301 }
302
303 /// Where the pointer is, for any event that has one — for a drag, where
304 /// it is now.
305 pub fn pointer(&self) -> Option<&Pointer> {
306 match self {
307 StepEditEvent::Hover { pointer, .. } | StepEditEvent::Click { pointer, .. } => {
308 Some(pointer)
309 }
310 StepEditEvent::Drag { to, .. } => Some(to),
311 _ => None,
312 }
313 }
314}
315
316/// A pointer or key event the editor passes on to an operation, having
317/// taken what is its own: picks, selecting, dragging handles (see
318/// the step editor, [`crate::step_editor`]).
319#[derive(Clone, Debug, PartialEq)]
320pub enum CanvasEvent {
321 /// The pointer moved over the viewport with no button held, `shift`
322 /// held or not.
323 Hover { pointer: Pointer, shift: bool },
324 /// The pointer left the viewport.
325 Leave,
326 /// A primary click that selected nothing: a double one, one while the
327 /// operation has a tool in hand, one on nothing selectable.
328 Click {
329 pointer: Pointer,
330 double: bool,
331 shift: bool,
332 },
333 /// What Escape does: end what is in progress — a chain being drawn,
334 /// the points picked for a tool — and, with nothing in progress, put
335 /// the tool in hand down. The selection is cleared already.
336 Cancel,
337 /// What Enter does: finish what is in progress, a
338 /// chain being drawn, a constraint to apply; the tool stays in hand.
339 Confirm,
340 /// What Delete and Backspace do: delete the selection.
341 Delete,
342 /// The draggable visual `key` dragged in the plane worked in, from
343 /// where it was grabbed to where the pointer is now — `pointer`, with
344 /// `shift` held or not. `done` on release.
345 Move {
346 key: String,
347 from: Vector3,
348 to: Vector3,
349 pointer: Pointer,
350 done: bool,
351 shift: bool,
352 },
353 /// A drag of the tool in hand, where it strokes (see
354 /// [`super::InHand::Strokes`]): the ray where it went down and the ray
355 /// where the pointer is now. `done` on release.
356 Stroke {
357 from: Pointer,
358 to: Pointer,
359 done: bool,
360 shift: bool,
361 },
362 /// A drag of the form's gizmo (see [`super::Gizmo`]): what it did
363 /// since it started, to apply to the step's arguments as they were
364 /// then — which they are again whenever this is sent. `done` on
365 /// release.
366 /// `from` is the gizmo as the step asked for it when the drag started:
367 /// where it stood, along what it stands on's own axes.
368 Gizmo {
369 drag: GizmoDrag,
370 from: Gizmo,
371 done: bool,
372 },
373 /// A selection box, with nothing in hand (see
374 /// [`StepEditEvent::SelectBox`]): what it takes of the selectable
375 /// visuals is the operation's to decide, from what they stand for, and
376 /// to select — added to the selection, if `add`.
377 SelectBox {
378 corners: [Pointer; 4],
379 crossing: bool,
380 add: bool,
381 },
382 /// Any other key, as the browser names it, a letter lowercased: a
383 /// tool's shortcut.
384 Key { key: String },
385 /// The view settled, `up` pointing up the screen (see
386 /// [`StepEditEvent::View`]).
387 View { up: Vector3 },
388}