Skip to main content

mathtex_editor_core/
menu.rs

1//! State of the swap or delete menu that Backspace opens next to a swappable structure.
2
3use crate::doc::{Document, NodeDoc};
4use crate::model::{MathClass, NodeId, Symbol, Tree};
5use crate::ops::{SwapKind, SwapVariant};
6
7/// What committing a row does.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum MenuItemKind {
10    /// Dissolves the structure into its content, always the first row so hosts can localize its label.
11    Delete,
12    /// Swaps the structure's delimiters, operator, accent, decoration, font, or environment.
13    Swap,
14}
15
16/// One row the host renders.
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct MenuItem {
19    /// The plain English row label, such as `ceiling` or `over: brace`.
20    pub label: String,
21    /// What the row does.
22    pub kind: MenuItemKind,
23}
24
25/// The rows and state of the open menu, its anchor rectangle comes from `Editor::render`.
26#[derive(Debug, Clone, PartialEq, Eq, Hash)]
27pub struct MenuView {
28    /// The visible rows, filtered by `query`.
29    pub items: Vec<MenuItem>,
30    /// The highlighted row index.
31    pub selected: usize,
32    /// The typed filter.
33    pub query: String,
34}
35
36/// A visible row effect.
37pub(crate) enum RowEffect<'a> {
38    Delete,
39    Swap(&'a SwapKind),
40}
41
42/// Editor private pending menu state.
43pub(crate) struct Menu {
44    pub(crate) anchor: NodeId,
45    variants: Vec<SwapVariant>,
46    pub(crate) query: String,
47    pub(crate) selected: usize,
48}
49
50impl Menu {
51    pub(crate) fn for_node(anchor: NodeId, variants: Vec<SwapVariant>) -> Self {
52        Self { anchor, variants, query: String::new(), selected: 0 }
53    }
54
55    fn visible_variants(&self) -> impl Iterator<Item = &SwapVariant> {
56        let q = self.query.to_lowercase();
57        self.variants.iter().filter(move |v| q.is_empty() || v.label.to_lowercase().contains(&q))
58    }
59
60    /// Row effects in display order, Delete pinned first.
61    pub(crate) fn visible(&self) -> Vec<RowEffect<'_>> {
62        std::iter::once(RowEffect::Delete).chain(self.visible_variants().map(|v| RowEffect::Swap(&v.kind))).collect()
63    }
64
65    pub(crate) fn view(&self) -> MenuView {
66        let delete = MenuItem { label: "Delete".to_string(), kind: MenuItemKind::Delete };
67        let swaps = self.visible_variants().map(|v| MenuItem { label: v.label.clone(), kind: MenuItemKind::Swap });
68        MenuView { items: std::iter::once(delete).chain(swaps).collect(), selected: self.selected, query: self.query.clone() }
69    }
70
71    /// Each visible row's preview, in the order of [`Menu::view`].
72    pub(crate) fn previews(&self, tree: &Tree) -> Vec<Option<Document>> {
73        let hollow = tree.node_to_doc(self.anchor).map(hollow);
74        let swaps = self.visible_variants().map(|v| hollow.as_ref().and_then(|h| preview(h, &v.kind)));
75        std::iter::once(None).chain(swaps).collect()
76    }
77}
78
79/// The structure with every slot emptied, a styled body holding a sample letter since a placeholder shows no font.
80pub(crate) fn hollow(node: NodeDoc) -> NodeDoc {
81    let mut node = node;
82    for slot in node.slots_mut() {
83        slot.clear();
84    }
85    if let NodeDoc::Styled { content, .. } = &mut node {
86        content.push(NodeDoc::Atom(Symbol { latex: "A".to_string(), class: MathClass::Ord }));
87    }
88    node
89}
90
91/// A document holding `hollow` with `kind` applied the way committing the row applies it.
92fn preview(hollow: &NodeDoc, kind: &SwapKind) -> Option<Document> {
93    let mut tree = Tree::from_doc(&Document::new(vec![hollow.clone()]));
94    let node = *tree.items(tree.root()).first()?;
95    tree.apply_swap(node, kind);
96    Some(tree.to_doc())
97}