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}