Skip to main content

geop_core_editor/
edit_form.rs

1//! [`EditForm`]: a [`Form`] built together with what setting each of its
2//! fields does — for an operation with many fields, whose events are then
3//! answered by the setter of the field they set (see [`EditForm::set`])
4//! rather than by matching their keys. Every setter gets the whole
5//! [`Edit`]: a field may set the step's arguments or its session.
6//!
7//! An operation only ever hands out the form ([`EditForm::into_form`]);
8//! the setters stay its own, built again for each event. A setter may capture what the form was built from — the part
9//! as the step sees it — for what setting a field implies for others:
10//! another sketch picked brings its own axis, a selection picks the
11//! construction it fits.
12
13use geop_core_program::{numeric::NumericInput, reader::Reader};
14
15use crate::{
16    editing::{
17        Action, CanvasEvent, Choice, Event, FileEntry, Form, Layout, Number, Table, TableEdit,
18        Value,
19    },
20    selectable::{Selectable, selected},
21};
22
23/// What a setter edits: the step's arguments, its session, and the keys of
24/// the visuals selected.
25pub struct Edit<'e, A, T> {
26    pub args: &'e mut A,
27    pub session: &'e mut T,
28    pub selection: &'e mut Vec<String>,
29}
30
31impl<A, T> Edit<'_, A, T> {
32    /// The same edit, to hand to one more setter.
33    pub fn again(&mut self) -> Edit<'_, A, T> {
34        Edit {
35            args: &mut *self.args,
36            session: &mut *self.session,
37            selection: &mut *self.selection,
38        }
39    }
40}
41
42/// What a step edited through an [`EditForm`] keeps while it is edited, as
43/// its editing state ([`crate::editable::Editable::EditingState`]): its
44/// session `T`, and the keys of the visuals selected.
45#[derive(Clone, Debug, Default)]
46pub struct FormState<T> {
47    pub session: T,
48    pub selection: Vec<String>,
49}
50
51impl<T: Clone> FormState<T> {
52    /// The arguments `args` and this state after `event`: a field set by
53    /// its setter in the form `form` makes of them as they were, the
54    /// selection replaced, and what the pointer or a key did passed to
55    /// `canvas`.
56    pub fn handle<'f, A: Clone>(
57        &self,
58        args: &A,
59        event: Event,
60        form: impl FnOnce(&A, &Self) -> EditForm<'f, A, T>,
61        canvas: impl FnOnce(Edit<'_, A, T>, &CanvasEvent),
62    ) -> (A, Self) {
63        let mut edited = args.clone();
64        let mut state = self.clone();
65        let edit = Edit {
66            args: &mut edited,
67            session: &mut state.session,
68            selection: &mut state.selection,
69        };
70        match event {
71            Event::Field { key, value } => form(args, self).set(&key, edit, value),
72            Event::Select(keys) => *edit.selection = keys,
73            Event::Canvas(event) => canvas(edit, &event),
74        }
75        (edited, state)
76    }
77}
78
79/// What setting a field does.
80type Setter<'a, A, T> = Box<dyn Fn(Edit<'_, A, T>, Value) + 'a>;
81
82/// A [`Form`] and what setting each of its fields does, for the arguments
83/// `A` and the session `T`.
84pub struct EditForm<'a, A, T = ()> {
85    pub form: Form,
86    setters: Vec<(String, Setter<'a, A, T>)>,
87}
88
89impl<A, T> Default for EditForm<'_, A, T> {
90    fn default() -> Self {
91        Self {
92            form: Form::default(),
93            setters: Vec::new(),
94        }
95    }
96}
97
98impl<'a, A, T> EditForm<'a, A, T> {
99    /// The form, as an editor is shown it.
100    pub fn into_form(self) -> Form {
101        self.form
102    }
103
104    /// What the field `key` does with `value`: its setter's. Nothing for a
105    /// key no field has — a field shown only to be read.
106    pub fn set(&self, key: &str, edit: Edit<'_, A, T>, value: Value) {
107        if let Some((_, setter)) = self.setters.iter().find(|(k, _)| k == key) {
108            setter(edit, value);
109        }
110    }
111
112    /// `setter` for the key `key`: for a control already in the dialog, or
113    /// one of its items — a list's entries each have their own key.
114    pub fn on(&mut self, key: impl Into<String>, setter: impl Fn(Edit<'_, A, T>, Value) + 'a) {
115        self.setters.push((key.into(), Box::new(setter)));
116    }
117
118    /// Shown as `layout` says (see [`Layout`]).
119    pub fn layout(&mut self, layout: Layout) -> &mut Self {
120        self.form.layout = layout;
121        self
122    }
123
124    /// Text typed — a name, a title — `set` to what is typed (see
125    /// [`crate::editing::Dialog::input`]).
126    pub fn input(
127        &mut self,
128        key: &str,
129        label: impl Into<String>,
130        value: impl Into<String>,
131        placeholder: Option<&str>,
132        set: impl Fn(Edit<'_, A, T>, &str) + 'a,
133    ) -> &mut Self {
134        self.form.dialog.input(key, label, value, placeholder);
135        self.on(key, move |edit, value| {
136            if let Value::Text(text) = value {
137                set(edit, &text);
138            }
139        });
140        self
141    }
142
143    /// A table edited in place (see [`Table`]): `edit` is run for each
144    /// thing done to it. A block pasted is taken apart here, into a row
145    /// added for each line below the last and the cells set, in reading
146    /// order — so `edit` never sees a [`TableEdit::Paste`], and a cell set
147    /// in a row just added finds the row there.
148    pub fn table(
149        &mut self,
150        key: &str,
151        table: Table,
152        edit: impl Fn(Edit<'_, A, T>, TableEdit) + 'a,
153    ) -> &mut Self {
154        let (rows, columns) = (table.rows.len(), table.columns.len());
155        self.form.dialog.table(key, table);
156        self.on(key, move |mut e, value| {
157            let Value::Table(what) = value else {
158                return;
159            };
160            let TableEdit::Paste { row, column, cells } = what else {
161                return edit(e, what);
162            };
163            let mut rows = rows;
164            for (r, line) in cells.into_iter().enumerate() {
165                let row = row + r;
166                while row >= rows {
167                    edit(e.again(), TableEdit::AddRow);
168                    rows += 1;
169                }
170                for (c, text) in line.into_iter().enumerate() {
171                    let column = column + c;
172                    if column < columns {
173                        edit(e.again(), TableEdit::Cell { row, column, text });
174                    }
175                }
176            }
177        });
178        self
179    }
180
181    /// Things to do or choose; pressing one runs `run` with its value.
182    pub fn actions(
183        &mut self,
184        key: &str,
185        actions: Vec<Action>,
186        run: impl Fn(Edit<'_, A, T>, &str) + 'a,
187    ) -> &mut Self {
188        self.form.dialog.actions(key, actions);
189        self.on(key, move |edit, value| {
190            if let Value::Choice(choice) = value {
191                run(edit, &choice);
192            }
193        });
194        self
195    }
196
197    pub fn checkbox(
198        &mut self,
199        key: &str,
200        label: impl Into<String>,
201        value: bool,
202        set: impl Fn(Edit<'_, A, T>, bool) + 'a,
203    ) -> &mut Self {
204        self.form.dialog.checkbox(key, label, value);
205        self.on(key, move |edit, value| {
206            if let Value::Bool(b) = value {
207                set(edit, b);
208            }
209        });
210        self
211    }
212
213    pub fn number(
214        &mut self,
215        key: &str,
216        number: Number,
217        set: impl Fn(Edit<'_, A, T>, f64) + 'a,
218    ) -> &mut Self {
219        self.form.dialog.number(key, number);
220        self.on(key, move |edit, value| {
221            if let Value::Number(v) = value {
222                set(edit, v);
223            }
224        });
225        self
226    }
227
228    /// A number field of a [`NumericInput`] (see [`Number::numeric`]):
229    /// text typed sets it — plain if it is a number, a formula otherwise —
230    /// and a number slid or dragged sets it plain. A formula that does not
231    /// evaluate is kept, as typed, its field saying why: the step then fails
232    /// naming it, and the text is there to be corrected.
233    pub fn numeric(
234        &mut self,
235        key: &str,
236        number: Number,
237        set: impl Fn(Edit<'_, A, T>, NumericInput) + 'a,
238    ) -> &mut Self {
239        self.form.dialog.number(key, number);
240        self.on(key, move |edit, value| {
241            if let Some(input) = value.numeric_input() {
242                set(edit, input);
243            }
244        });
245        self
246    }
247
248    /// One of `options`, by value — found by typing, if `searchable`.
249    pub fn select(
250        &mut self,
251        key: &str,
252        label: impl Into<String>,
253        value: impl Into<String>,
254        options: Vec<Choice>,
255        searchable: bool,
256        set: impl Fn(Edit<'_, A, T>, &str) + 'a,
257    ) -> &mut Self {
258        (self.form.dialog).select(key, label, value, options, searchable);
259        self.on(key, move |edit, value| {
260            if let Value::Choice(choice) = value {
261                set(edit, &choice);
262            }
263        });
264        self
265    }
266
267    /// A file the program reads or places (see
268    /// [`crate::editing::Dialog::file`]).
269    pub fn file(
270        &mut self,
271        key: &str,
272        label: impl Into<String>,
273        value: impl Into<String>,
274        files: Vec<FileEntry>,
275        upload: &[&str],
276        set: impl Fn(Edit<'_, A, T>, &str) + 'a,
277    ) -> &mut Self {
278        self.form.dialog.file(key, label, value, files, upload);
279        self.on(key, move |edit, value| {
280            if let Value::Choice(choice) = value {
281                set(edit, &choice);
282            }
283        });
284        self
285    }
286
287    /// A colour, `#rrggbb`.
288    pub fn color(
289        &mut self,
290        key: &str,
291        label: impl Into<String>,
292        value: impl Into<String>,
293        set: impl Fn(Edit<'_, A, T>, &str) + 'a,
294    ) -> &mut Self {
295        self.form.dialog.color(key, label, value);
296        self.on(key, move |edit, value| {
297            if let Value::Text(color) = value {
298                set(edit, &color);
299            }
300        });
301        self
302    }
303
304    /// Values picked, holding `value`, each selected by a click as `P`
305    /// says in the part `reader` reads (see [`Selectable`]): `set` gets what
306    /// the field holds after a pick — the values held, and the one a click
307    /// selected, or without the one it took out.
308    pub fn pick<P: Selectable>(
309        &mut self,
310        key: &str,
311        label: impl Into<String>,
312        reader: &Reader<'a>,
313        value: &[P],
314        multiple: bool,
315        set: impl Fn(Edit<'_, A, T>, Vec<P>) + 'a,
316    ) -> &mut Self {
317        (self.form.dialog).pick(key, label, reader, value, multiple);
318        let reader = reader.clone();
319        self.on(key, move |edit, value| {
320            if let Value::Elements(picks) = value {
321                set(edit, selected(&reader, &picks));
322            }
323        });
324        self
325    }
326
327    /// One value picked, holding `value` if there is one (see
328    /// [`EditForm::pick`]): `set` gets the value a click picks. Emptying
329    /// the field sets nothing: what a step needs one of is never none.
330    pub fn pick_one<P: Selectable>(
331        &mut self,
332        key: &str,
333        label: impl Into<String>,
334        reader: &Reader<'a>,
335        value: Option<&P>,
336        set: impl Fn(Edit<'_, A, T>, P) + 'a,
337    ) -> &mut Self {
338        let value = value.map(std::slice::from_ref).unwrap_or_default();
339        self.pick(key, label, reader, value, false, move |edit, mut picked| {
340            if let Some(one) = picked.pop() {
341                set(edit, one);
342            }
343        })
344    }
345}
346
347/// A select field's table of options, each `(option, value, label)`: the
348/// value it gives `chosen`, and the choices it offers.
349pub fn choices<T: PartialEq>(options: &[(T, &str, &str)], chosen: &T) -> (String, Vec<Choice>) {
350    let value = (options.iter().find(|o| o.0 == *chosen)).map_or("", |o| o.1);
351    let choices = (options.iter()).map(|&(_, v, l)| Choice::new(v, l));
352    (value.to_string(), choices.collect())
353}
354
355/// The option of a select field's table `options` (see [`choices`]) that
356/// has the value `value`.
357pub fn chosen<T: Copy>(options: &[(T, &str, &str)], value: &str) -> Option<T> {
358    options.iter().find(|o| o.1 == value).map(|o| o.0)
359}
360
361#[cfg(test)]
362mod tests {
363    use super::*;
364    use crate::editing::{Cell, Column, ColumnKind, TableRow};
365
366    /// A block pasted into a table reaches the operation as the rows added
367    /// below the last and the cells set, in reading order, and never as a
368    /// paste; what runs past the last column is dropped.
369    #[test]
370    fn a_paste_is_taken_apart_into_rows_added_and_cells_set() {
371        let columns = vec![
372            Column::new("a", ColumnKind::Text),
373            Column::new("b", ColumnKind::Text),
374        ];
375        let rows = vec![TableRow::new(vec![Cell::new(""), Cell::new("")])];
376        let table = Table::new(columns, rows, "");
377        let mut form = EditForm::<Vec<TableEdit>>::default();
378        form.table("t", table, |edit, what| edit.args.push(what));
379        let mut seen = Vec::new();
380        let mut state = FormState::<()>::default();
381        let edit = Edit {
382            args: &mut seen,
383            session: &mut state.session,
384            selection: &mut state.selection,
385        };
386        let paste = TableEdit::Paste {
387            row: 0,
388            column: 1,
389            cells: vec![
390                vec!["x".into(), "over".into()],
391                vec!["y".into()],
392                vec!["z".into()],
393            ],
394        };
395        form.set("t", edit, Value::Table(paste));
396        let cell = |row, column, text: &str| TableEdit::Cell {
397            row,
398            column,
399            text: text.into(),
400        };
401        assert_eq!(
402            seen,
403            [
404                cell(0, 1, "x"),
405                TableEdit::AddRow,
406                cell(1, 1, "y"),
407                TableEdit::AddRow,
408                cell(2, 1, "z"),
409            ]
410        );
411    }
412}