Skip to main content

geop_core_editor/
editable.rs

1//! [`Editable`]: what an [`Operation`] is to an editor — how it is offered,
2//! the arguments of a new step, the form a step is edited through and what
3//! the user's events do to it — and [`Editors`], the operations an editor
4//! offers, which also gives the program runner its registry.
5
6use std::{any::Any, collections::BTreeMap, rc::Rc};
7
8use geop_core_math::geop_error::{GeopError, GeopResult};
9use geop_core_program::{
10    operation::{ErasedOperation, Operation, args, saved},
11    operation_registry::OperationRegistry,
12    part::Part,
13    program::{Program, ProgramStep},
14    reader::Reader,
15    runner::Runner,
16};
17use serde::Serialize;
18
19use crate::editing::{Event, Form, hit::Scene};
20
21thread_local! {
22    static FORMS_BUILT: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
23}
24
25/// How many forms of the step edited this thread has built
26/// ([`Editors::presentation`]) ever: what a test bounds the work of a
27/// pointer event by, as [`crate::view::parts_drawn`] does drawing — a form
28/// can ask the whole assembly about what it built.
29pub fn forms_built() -> usize {
30    FORMS_BUILT.get()
31}
32
33/// What an operation is to an editor: its short name and what it does —
34/// what an editor offers it by — and where and how prominently the editor
35/// offers it.
36#[derive(Clone, Debug, PartialEq, Serialize)]
37pub struct OperationInfo {
38    pub label: &'static str,
39    pub doc: &'static str,
40    pub group: OperationGroup,
41    pub tier: OperationTier,
42    /// Not yet robust enough to offer by default: an editor's reduced mode
43    /// leaves it out.
44    pub experimental: bool,
45    /// Whether its reference fields pick in the part as the step builds it,
46    /// rather than as it was before: a placed part's mates pick elements of
47    /// the part it places.
48    pub picks_built: bool,
49    /// Whether its reference fields pick in the part as the whole program
50    /// builds it, the steps after its own too — and its step is edited with
51    /// them built. For a step that says something of the finished part and
52    /// reads none of it, wherever in the program it stands: the properties'
53    /// colours of faces, usually the first step, of faces later steps make.
54    #[serde(skip)]
55    pub picks_whole: bool,
56    /// The extensions of the files its steps read that are no programs —
57    /// data a program keeps beside it, a STEP file: what a front end keeps
58    /// in a workspace with the programs, and sends the editor.
59    pub reads: &'static [&'static str],
60    /// What it is shown as: SVG on a 20 × 20 grid, drawn in the current
61    /// colour — strokes `currentColor`, unfilled unless it says otherwise —
62    /// that a front end puts in an `<svg>` of its own. None to show its
63    /// label.
64    pub icon: Option<&'static str>,
65    /// The key that starts a new step of it while none is edited — a
66    /// letter, `Shift+` one for the less used — as every front end offers
67    /// and shows it: `E`, `Shift+F`. Unique among the operations offered;
68    /// `F` and `Home` are the view's (framing it), `?` lists the keys.
69    pub shortcut: Option<&'static str>,
70    /// Whether Escape, with nothing in progress in a step of it, puts the
71    /// step into the program as OK does, rather than cancelling it: a
72    /// sketch is drawing its user expects kept, as every CAD program keeps
73    /// a sketch left by Escape (see [`crate::editor::Editor::handle`]).
74    #[serde(skip)]
75    pub kept_on_escape: bool,
76}
77
78impl OperationInfo {
79    /// An operation offered by default, whose reference fields pick in the
80    /// part as it was before the step.
81    pub const fn new(
82        label: &'static str,
83        doc: &'static str,
84        group: OperationGroup,
85        tier: OperationTier,
86    ) -> Self {
87        Self {
88            label,
89            doc,
90            group,
91            tier,
92            experimental: false,
93            picks_built: false,
94            picks_whole: false,
95            reads: &[],
96            icon: None,
97            shortcut: None,
98            kept_on_escape: false,
99        }
100    }
101
102    /// The same, but left out of an editor's reduced mode.
103    pub const fn experimental(self) -> Self {
104        Self {
105            experimental: true,
106            ..self
107        }
108    }
109
110    /// The same, but picking in the part as the step builds it.
111    pub const fn picks_built(self) -> Self {
112        Self {
113            picks_built: true,
114            ..self
115        }
116    }
117
118    /// The same, but picking in the part as the whole program builds it.
119    pub const fn picks_whole(self) -> Self {
120        Self {
121            picks_whole: true,
122            ..self
123        }
124    }
125
126    /// The same, its steps reading files of the extensions `reads`.
127    pub const fn reads(self, reads: &'static [&'static str]) -> Self {
128        Self { reads, ..self }
129    }
130
131    /// The same, started by the key `shortcut` (see
132    /// [`OperationInfo::shortcut`]).
133    pub const fn shortcut(self, shortcut: &'static str) -> Self {
134        Self {
135            shortcut: Some(shortcut),
136            ..self
137        }
138    }
139
140    /// The same, kept when Escape leaves a step of it (see
141    /// [`OperationInfo::kept_on_escape`]).
142    pub const fn kept_on_escape(self) -> Self {
143        Self {
144            kept_on_escape: true,
145            ..self
146        }
147    }
148
149    /// The same, shown as `icon` (see [`OperationInfo::icon`]).
150    pub const fn icon(self, icon: &'static str) -> Self {
151        Self {
152            icon: Some(icon),
153            ..self
154        }
155    }
156}
157
158/// An operation as an editor lists it: the name its steps are stored
159/// under, and its [`OperationInfo`].
160#[derive(Clone, Debug, PartialEq, Serialize)]
161pub struct Listed {
162    pub kind: &'static str,
163    #[serde(flatten)]
164    pub info: &'static OperationInfo,
165}
166
167/// What an operation works on, as an editor groups its operations: the
168/// toolbar's sections, in this order — the order of a part's workflow,
169/// the part's own properties after its shape.
170/// Serialized as the group's name, as it is shown.
171#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
172pub enum OperationGroup {
173    /// Sketches, and the reference geometry they are drawn on.
174    Sketch,
175    /// What makes a solid of a sketch: extrusions, revolutions, sweeps and
176    /// lofts.
177    Solid,
178    /// Features that change a solid: holes, rounds, walls, threads.
179    Features,
180    /// Whole bodies: patterned, mirrored, combined, cut, moved.
181    #[serde(rename = "Pattern & bodies")]
182    Bodies,
183    /// Faces standing on their own, and freeform shapes.
184    Surface,
185    /// Bodies of bent sheet, and their flat patterns.
186    #[serde(rename = "Sheet metal")]
187    SheetMetal,
188    /// What a part is besides its shape: its parameters, and what it is
189    /// called, made of and coloured.
190    Part,
191    /// Placed parts, and what runs between them.
192    Assembly,
193    /// What is made of a part rather than in it: drawings, exports.
194    Output,
195}
196
197/// How prominently an editor offers an operation within its group, from
198/// the few used most to the rarely used: the order the group lists them in.
199#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
200pub enum OperationTier {
201    /// One of the few used most: a big button.
202    Big,
203    /// Used often: a small button.
204    Small,
205    /// Used rarely: only in its group's menu.
206    Menu,
207}
208
209/// What a step is edited against: its id — what everything it builds is
210/// named after — the runner it is run by, which reads the files around its
211/// program, the part as it was before it, the part as the step built it
212/// when it last ran, if it did, and, for an event, the part as the editor
213/// draws it — what a pointer is over, without drawing it again.
214pub struct Context<'a> {
215    pub step: &'a str,
216    pub runner: &'a Runner,
217    pub before: Reader<'a>,
218    pub built: Option<Reader<'a>>,
219    /// The part as far as the last run built it: all of it, for a step that
220    /// picks in the whole part ([`OperationInfo::picks_whole`]), whose
221    /// fields read it here.
222    pub whole: Reader<'a>,
223    /// The part as drawn for picks (see [`Scene`]): given with every event
224    /// an editor passes on, and nothing else.
225    pub scene: Option<&'a dyn Scene>,
226}
227
228/// An [`Operation`] an editor offers: everything it keeps while a step is
229/// edited is its editing state, and everything it shows the step's form.
230pub trait Editable: Operation {
231    const INFO: OperationInfo;
232    /// What the editor keeps about a step while it is edited, beyond its
233    /// arguments, starting from the default whenever a step is opened.
234    type EditingState: Default + 'static;
235
236    /// The arguments of a new step: sensible defaults, taking the part it
237    /// is inserted into into account.
238    fn new_args(context: &Context) -> Self::Args;
239
240    /// What the editor shows while the step is edited. Never fails: an
241    /// editor needs the form most exactly when the arguments do not build,
242    /// so whatever is wrong is said in it.
243    fn presentation(context: &Context, args: &Self::Args, editing: &Self::EditingState) -> Form;
244
245    /// The step's arguments and editing state after `event`.
246    fn process_user_event(
247        context: &Context,
248        args: &Self::Args,
249        editing: &Self::EditingState,
250        event: Event,
251    ) -> (Self::Args, Self::EditingState);
252}
253
254/// A step's arguments, as saved, and its editing state after an event.
255pub struct Edited {
256    pub args: serde_json::Value,
257    pub editing: Box<dyn Any>,
258}
259
260/// An [`Editable`] with its types erased, so that operations of all kinds
261/// can be kept in one [`Editors`]. Arguments come as saved.
262pub trait ErasedEditable: ErasedOperation {
263    fn info(&self) -> &'static OperationInfo;
264    fn new_args(&self, context: &Context) -> GeopResult<serde_json::Value>;
265    fn initial_editing(&self) -> Box<dyn Any>;
266    fn presentation(
267        &self,
268        context: &Context,
269        args: &serde_json::Value,
270        editing: &dyn Any,
271    ) -> GeopResult<Form>;
272    fn process_user_event(
273        &self,
274        context: &Context,
275        args: &serde_json::Value,
276        editing: &dyn Any,
277        event: Event,
278    ) -> GeopResult<Edited>;
279}
280
281fn editing<O: Editable>(editing: &dyn Any) -> GeopResult<&O::EditingState> {
282    editing.downcast_ref().ok_or_else(|| {
283        GeopError::new(format!(
284            "operation `{}` was given an editing state that is not `{}`",
285            O::KIND,
286            std::any::type_name::<O::EditingState>()
287        ))
288    })
289}
290
291impl<O: Editable> ErasedEditable for O {
292    fn info(&self) -> &'static OperationInfo {
293        &O::INFO
294    }
295
296    fn new_args(&self, context: &Context) -> GeopResult<serde_json::Value> {
297        saved::<O>(&<O as Editable>::new_args(context))
298    }
299
300    fn initial_editing(&self) -> Box<dyn Any> {
301        Box::new(O::EditingState::default())
302    }
303
304    fn presentation(
305        &self,
306        context: &Context,
307        args: &serde_json::Value,
308        editing: &dyn Any,
309    ) -> GeopResult<Form> {
310        Ok(<O as Editable>::presentation(
311            context,
312            &self::args::<O>(args)?,
313            self::editing::<O>(editing)?,
314        ))
315    }
316
317    fn process_user_event(
318        &self,
319        context: &Context,
320        args: &serde_json::Value,
321        editing: &dyn Any,
322        event: Event,
323    ) -> GeopResult<Edited> {
324        let (args, editing) = <O as Editable>::process_user_event(
325            context,
326            &self::args::<O>(args)?,
327            self::editing::<O>(editing)?,
328            event,
329        );
330        Ok(Edited {
331            args: saved::<O>(&args)?,
332            editing: Box::new(editing),
333        })
334    }
335}
336
337thread_local! {
338    static LISTED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
339}
340
341/// How many forms this thread has made for a list of steps
342/// ([`Editors::listed`]), ever: what listing the steps costs, so a test
343/// can bound it by a count rather than time it.
344pub fn forms_listed() -> usize {
345    LISTED.get()
346}
347
348/// The operations an editor offers, by name, in the order they were added:
349/// the order an editor offers them in within a group and tier. The runner
350/// its programs run on is given the same operations by the
351/// [`crate::registry::Registry`] that holds them.
352#[derive(Default)]
353pub struct Editors {
354    by_kind: BTreeMap<&'static str, Rc<dyn ErasedEditable>>,
355    order: Vec<&'static str>,
356}
357
358/// The part a step is edited against, as a run left it — and, for a step
359/// of a part placed in the program, edited where it is placed, the part it
360/// is placed in, as the step placing it left it: what the step reads there
361/// in context (see [`geop_core_program::placed::OUTSIDE`]). A part of its
362/// own is read as itself.
363#[derive(Clone)]
364pub struct Against<'a> {
365    pub part: &'a Part,
366    pub outside: Option<Reader<'a>>,
367}
368
369impl<'a> From<&'a Part> for Against<'a> {
370    fn from(part: &'a Part) -> Self {
371        Self {
372            part,
373            outside: None,
374        }
375    }
376}
377
378impl<'a> Against<'a> {
379    /// `reader`, of the part, placed where it is.
380    fn placed(&self, reader: Reader<'a>) -> Reader<'a> {
381        match &self.outside {
382            Some(outside) => reader.placed_in(outside.clone()),
383            None => reader,
384        }
385    }
386
387    /// The part as it is now (see [`Part::current`]).
388    pub fn current(&self) -> Reader<'a> {
389        self.placed(self.part.current())
390    }
391
392    /// The part after the step `step` of the last run (see [`Part::after`]).
393    pub fn after(&self, step: &str) -> Reader<'a> {
394        self.placed(self.part.after(step))
395    }
396
397    /// The part as it is before the step `step` of `program`: before that
398    /// step as the last run ran it — or, for a step the last run did not
399    /// run, after the last step before it in `program` that it did.
400    pub fn before(&self, program: &Program, step: &str) -> GeopResult<Reader<'a>> {
401        let part = self.part;
402        let position = (program.steps.iter().position(|s| s.id == step))
403            .ok_or_else(|| GeopError::new(format!("step `{step}` is not in the program")))?;
404        let ran = |id: &str| part.order().iter().any(|ran| ran == id);
405        if ran(step) {
406            return Ok(self.placed(part.before(step)));
407        }
408        let earlier = (program.steps.iter().take(position).rev()).find(|s| ran(&s.id));
409        Ok(self.placed(match (earlier, program.steps.first()) {
410            (Some(earlier), _) => part.after(&earlier.id),
411            (None, Some(first)) if ran(&first.id) || position == 0 => part.before(&first.id),
412            _ => part.current(),
413        }))
414    }
415}
416
417/// A step being edited: its arguments as saved, its operation, and what
418/// it is edited against.
419struct Step<'a> {
420    args: &'a serde_json::Value,
421    operation: &'a dyn ErasedEditable,
422    context: Context<'a>,
423}
424
425impl Editors {
426    /// Adds `operation`. Fails if an operation of its name is already there.
427    pub fn with<O: Editable>(mut self, operation: O) -> GeopResult<Self> {
428        self.add(Rc::new(operation))?;
429        Ok(self)
430    }
431
432    /// Adds `operation`, erased. Fails if an operation of its name is
433    /// already there.
434    pub(crate) fn add(&mut self, operation: Rc<dyn ErasedEditable>) -> GeopResult<()> {
435        let kind = operation.kind();
436        if self.by_kind.contains_key(kind) {
437            return Err(GeopError::new(format!(
438                "operation `{kind}` is registered twice"
439            )));
440        }
441        self.by_kind.insert(kind, operation);
442        self.order.push(kind);
443        Ok(())
444    }
445
446    /// The operations, as a program runner runs them.
447    pub fn operations(&self) -> GeopResult<OperationRegistry> {
448        let mut registry = OperationRegistry::default();
449        for operation in self.by_kind.values() {
450            let operation: Rc<dyn ErasedOperation> = operation.clone();
451            registry = registry.with_erased(operation)?;
452        }
453        Ok(registry)
454    }
455
456    /// What each operation is, in the order they were added.
457    pub fn infos(&self) -> Vec<Listed> {
458        (self.order.iter())
459            .filter_map(|kind| self.info(kind))
460            .collect()
461    }
462
463    /// What the operation `kind` is, if it is offered.
464    pub fn info(&self, kind: &str) -> Option<Listed> {
465        (self.by_kind.get_key_value(kind)).map(|(&kind, operation)| Listed {
466            kind,
467            info: operation.info(),
468        })
469    }
470
471    fn get(&self, kind: &str) -> GeopResult<&dyn ErasedEditable> {
472        self.by_kind
473            .get(kind)
474            .map(Rc::as_ref)
475            .ok_or_else(|| GeopError::new(format!("unknown operation `{kind}`")))
476    }
477
478    /// The step `step` of `program` and its operation, and what it is
479    /// edited against in `part`, as `runner` last ran it: the part before
480    /// it and after it.
481    fn step<'a>(
482        &'a self,
483        runner: &'a Runner,
484        part: Against<'a>,
485        program: &'a Program,
486        step: &'a str,
487    ) -> GeopResult<Step<'a>> {
488        let program_step = program.step(step)?;
489        let context = Context {
490            step,
491            runner,
492            before: part.before(program, step)?,
493            built: Some(part.after(step)),
494            whole: part.current(),
495            scene: None,
496        };
497        Ok(Step {
498            args: &program_step.args,
499            operation: self.get(&program_step.operation)?,
500            context,
501        })
502    }
503
504    /// A new step of the operation `kind`, `id`, to insert into `program`
505    /// before its step `before`, or after its last.
506    pub fn new_step<'a>(
507        &self,
508        runner: &Runner,
509        part: impl Into<Against<'a>>,
510        program: &Program,
511        kind: &str,
512        id: &str,
513        before: Option<&str>,
514    ) -> GeopResult<ProgramStep> {
515        let operation = self.get(kind)?;
516        let part = part.into();
517        let before = match before {
518            Some(step) => part.before(program, step)?,
519            None => part.current(),
520        };
521        let context = Context {
522            step: id,
523            runner,
524            before,
525            built: None,
526            whole: part.current(),
527            scene: None,
528        };
529        Ok(ProgramStep {
530            id: id.to_string(),
531            operation: operation.kind().to_string(),
532            args: operation.new_args(&context)?,
533            active: None,
534        })
535    }
536
537    /// The editing state the step `step` starts with.
538    pub fn initial_editing(&self, program: &Program, step: &str) -> GeopResult<Box<dyn Any>> {
539        Ok(self.get(&program.step(step)?.operation)?.initial_editing())
540    }
541
542    /// What the editor shows while the step `step` is edited.
543    pub fn presentation<'a>(
544        &self,
545        runner: &Runner,
546        part: impl Into<Against<'a>>,
547        program: &Program,
548        step: &str,
549        editing: &dyn Any,
550    ) -> GeopResult<Form> {
551        FORMS_BUILT.set(FORMS_BUILT.get() + 1);
552        let step = self.step(runner, part.into(), program, step)?;
553        (step.operation).presentation(&step.context, step.args, editing)
554    }
555
556    /// What a list of steps shows of the step `step`: its form, as a step
557    /// just opened shows it, against the part before it — and never what
558    /// it built, so that a list of every step asks nothing of what each
559    /// built. Counted ([`forms_listed`]).
560    pub fn listed<'a>(
561        &self,
562        runner: &Runner,
563        part: impl Into<Against<'a>>,
564        program: &Program,
565        step: &str,
566    ) -> GeopResult<Form> {
567        LISTED.set(LISTED.get() + 1);
568        let mut step = self.step(runner, part.into(), program, step)?;
569        step.context.built = None;
570        let editing = step.operation.initial_editing();
571        (step.operation).presentation(&step.context, step.args, &*editing)
572    }
573
574    /// The arguments and editing state of the step `step` after `event`,
575    /// with what the pointer is over found in `scene`, if one is given.
576    #[allow(clippy::too_many_arguments)]
577    pub fn process_user_event<'a>(
578        &self,
579        runner: &Runner,
580        part: impl Into<Against<'a>>,
581        program: &Program,
582        step: &str,
583        editing: &dyn Any,
584        event: Event,
585        scene: Option<&dyn Scene>,
586    ) -> GeopResult<Edited> {
587        let mut step = self.step(runner, part.into(), program, step)?;
588        step.context.scene = scene;
589        (step.operation).process_user_event(&step.context, step.args, editing, event)
590    }
591}