Skip to main content

geop_core_editor/output/
inspect.rs

1//! [`Inspector`]: a question answered about what a program built — of the
2//! whole part (its mass properties), or of elements picked in it (what
3//! they measure).
4
5use geop_core_math::{
6    geop_error::{GeopError, GeopResult},
7    primitives::CoordinateSystem,
8};
9use geop_core_program::element::PickedElement;
10use serde::{Serialize, de::DeserializeOwned};
11
12use super::{Output, read_options};
13use crate::editing::{Tone, Visual};
14
15/// What an inspector is to a front end: how it is asked, what it is
16/// offered as, and whether it is asked of the whole part or of picks.
17#[derive(Clone, Debug, PartialEq, Serialize)]
18pub struct InspectorInfo {
19    /// The name it is asked by, and its answers are of: `mass_properties`.
20    pub kind: &'static str,
21    /// What it is offered as: `Mass`.
22    pub label: &'static str,
23    /// What it answers, in a sentence.
24    pub doc: &'static str,
25    /// What it is shown as (see
26    /// [`crate::editable::OperationInfo::icon`]).
27    pub icon: Option<&'static str>,
28    /// What it is asked of, if not the whole part: a tool that picks these
29    /// in the viewport, and answers again with every pick.
30    pub picks: Option<Picks>,
31}
32
33/// What an inspector that picks takes: elements of what an editor draws of
34/// the kinds `accepts` (see [`crate::editing::hit::Scene::pick`]),
35/// at most `most` of them — another pick then replaces the oldest. What
36/// each is to the inspector, it selects itself (see
37/// [`crate::selectable`]).
38#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
39pub struct Picks {
40    pub accepts: &'static [&'static str],
41    pub most: usize,
42}
43
44/// A question asked of what a program built. An inspector is a unit
45/// struct; what it is told beyond the part is its [`Inspector::Options`].
46pub trait Inspector: 'static {
47    const INFO: InspectorInfo;
48    /// What it is told besides the part, as a front end sends it (JSON);
49    /// the defaults where it sends none.
50    type Options: DeserializeOwned + Default;
51    /// Its answer, as a front end is sent it.
52    type Report: Serialize;
53
54    /// The answer about `output` — of the elements `picked`, for one that
55    /// picks ([`InspectorInfo::picks`]) — and what to draw of it in the
56    /// viewport: the points a distance is measured between.
57    fn inspect(
58        output: &mut Output,
59        picked: &[PickedElement],
60        options: Self::Options,
61    ) -> GeopResult<(Self::Report, Vec<Visual>)>;
62
63    /// `report` as a front end shows it: what lets every front end show
64    /// every inspector's answer alike. Nothing, by default: the report is
65    /// shown as it is.
66    fn show(report: &Self::Report) -> Answer {
67        let _ = report;
68        Answer::default()
69    }
70}
71
72/// An inspector's answer as a front end shows it, whatever the inspector:
73/// text, tables and groups of them, and what to do next — built by the
74/// inspector ([`Inspector::show`]), so that a front end knows none.
75#[derive(Clone, Debug, Default, PartialEq, Serialize)]
76pub struct Answer {
77    pub blocks: Vec<Block>,
78    /// A plane the answer is of — a planar face or a datum plane picked —
79    /// to cut a section view with.
80    pub plane: Option<CoordinateSystem>,
81}
82
83/// A part of an [`Answer`].
84#[derive(Clone, Debug, PartialEq, Serialize)]
85#[serde(tag = "type", rename_all = "snake_case")]
86pub enum Block {
87    /// A sentence: a hint, or why something failed.
88    Text { text: String, tone: Tone },
89    /// Items one under another: what was picked, which solids overlap.
90    List { items: Vec<Row> },
91    /// Rows of cells, under a head if it has one, over a foot if it has
92    /// one: each row's label, if it has one, in the first column.
93    Table {
94        head: Vec<String>,
95        rows: Vec<Row>,
96        foot: Vec<Row>,
97    },
98    /// Blocks under a title, shown open or folded: one solid's mass.
99    Group {
100        title: String,
101        /// Said after the title: what it is made of.
102        detail: Option<String>,
103        open: bool,
104        /// What to light while the title is pointed at.
105        light: Vec<PickedElement>,
106        blocks: Vec<Block>,
107    },
108    /// Buttons, side by side.
109    Actions { actions: Vec<AnswerAction> },
110}
111
112/// A row of a [`Block::Table`], or an item of a [`Block::List`].
113#[derive(Clone, Debug, Default, PartialEq, Serialize)]
114pub struct Row {
115    pub label: Option<String>,
116    /// Its cells, each text of one or more lines.
117    pub cells: Vec<String>,
118    /// Said in full while it is pointed at.
119    pub detail: Option<String>,
120    /// What to light while it is pointed at.
121    pub light: Vec<PickedElement>,
122    pub tone: Tone,
123}
124
125impl Row {
126    /// The row `label`: `cells`.
127    pub fn labelled(label: impl Into<String>, cells: Vec<String>) -> Self {
128        Self {
129            label: Some(label.into()),
130            cells,
131            ..Self::default()
132        }
133    }
134
135    /// The row of `cells`, with no label.
136    pub fn of(cells: Vec<String>) -> Self {
137        Self {
138            cells,
139            ..Self::default()
140        }
141    }
142}
143
144/// A button of an [`Answer`].
145#[derive(Clone, Debug, PartialEq, Serialize)]
146pub struct AnswerAction {
147    pub label: String,
148    /// What it does.
149    pub doc: String,
150    /// Whether it is what the answer shown was asked with: lit.
151    pub chosen: bool,
152    pub does: Does,
153}
154
155/// What pressing an [`AnswerAction`] does.
156#[derive(Clone, Debug, PartialEq, Serialize)]
157#[serde(tag = "type", rename_all = "snake_case")]
158pub enum Does {
159    /// Asks the inspector again, told `options`.
160    Inspect { options: serde_json::Value },
161    /// Saves what the exporter `exporter` writes, told `options`.
162    Export {
163        exporter: &'static str,
164        options: serde_json::Value,
165    },
166}
167
168/// An [`Inspector`] with its types erased, so that inspectors of all kinds
169/// can be kept in one [`super::Outputs`]. Every inspector is one, through
170/// its value.
171pub trait ErasedInspector {
172    fn info(&self) -> &'static InspectorInfo;
173
174    /// The answer about `output`, of `picked`, with `options` as sent.
175    fn inspect(
176        &self,
177        output: &mut Output,
178        picked: &[PickedElement],
179        options: &serde_json::Value,
180    ) -> GeopResult<Inspection>;
181}
182
183impl<I: Inspector> ErasedInspector for I {
184    fn info(&self) -> &'static InspectorInfo {
185        &I::INFO
186    }
187
188    fn inspect(
189        &self,
190        output: &mut Output,
191        picked: &[PickedElement],
192        options: &serde_json::Value,
193    ) -> GeopResult<Inspection> {
194        let kind = I::INFO.kind;
195        let ctx =
196            |e: GeopError| e.with_context(format!("inspect(inspector={kind}, picked={picked:?})"));
197        let (report, visuals) =
198            I::inspect(output, picked, read_options::<I::Options>(kind, options)?).map_err(ctx)?;
199        let shown = I::show(&report);
200        let report = serde_json::to_value(report)
201            .map_err(|e| ctx(GeopError::new(format!("writing the answer: {e}"))))?;
202        Ok(Inspection {
203            kind,
204            report,
205            shown,
206            visuals,
207        })
208    }
209}
210
211/// An inspector's answer, as an update carries it.
212#[derive(Clone, Debug, PartialEq, Serialize)]
213pub struct Inspection {
214    /// The inspector's kind: what `report` is.
215    pub kind: &'static str,
216    /// The answer as data, for a program to read.
217    pub report: serde_json::Value,
218    /// The answer as a front end shows it.
219    pub shown: Answer,
220    /// What to draw of it in the viewport: sent with the tool that picks.
221    #[serde(skip)]
222    pub visuals: Vec<Visual>,
223}