1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//! Build menu trees.
//!
//! Menus are a way to arrange many actions in groups of more manageable size.
//!
//! A menu can be seen as a `MenuTree`. It has a list of children:
//!
//! * Leaf nodes are made of a label and a callback
//! * Sub-trees are made of a label, and another `MenuTree`.
//! * Delimiters are just there to separate groups of related children.
//!
//! The [menubar] is the main way to show menus.
//!
//! [menubar]: ../struct.Cursive.html#method.menubar

use Cursive;
use With;
use event::Callback;
use std::rc::Rc;

/// Root of a menu tree.
#[derive(Default, Clone)]
pub struct MenuTree {
    /// Menu items
    pub children: Vec<MenuItem>,
}

/// Node in the menu tree.
#[derive(Clone)]
pub enum MenuItem {
    /// Actionnable button with a label.
    Leaf(String, Callback),
    /// Sub-menu with a label.
    Subtree(String, Rc<MenuTree>),
    /// Delimiter without a label.
    Delimiter,
}

impl MenuItem {
    /// Returns the label for this item.
    ///
    /// Returns an empty string if `self` is a delimiter.
    pub fn label(&self) -> &str {
        match *self {
            MenuItem::Delimiter => "│",
            MenuItem::Leaf(ref label, _) | MenuItem::Subtree(ref label, _) => {
                label
            }
        }
    }

    /// Returns `true` if `self` is a delimiter.
    pub fn is_delimiter(&self) -> bool {
        match *self {
            MenuItem::Delimiter => true,
            _ => false,
        }
    }

    /// Returns `true` if `self` is a leaf node.
    pub fn is_leaf(&self) -> bool {
        match *self {
            MenuItem::Leaf(_, _) => true,
            _ => false,
        }
    }

    /// Returns `true` if `self` is a subtree.
    pub fn is_subtree(&self) -> bool {
        match *self {
            MenuItem::Subtree(_, _) => true,
            _ => false,
        }
    }

    /// Return a mutable reference to the subtree, if applicable.
    ///
    /// Returns `None` if `self` is not a `MenuItem::Subtree`.
    pub fn as_subtree(&mut self) -> Option<&mut MenuTree> {
        match *self {
            MenuItem::Subtree(_, ref mut tree) => Some(Rc::make_mut(tree)),
            _ => None,
        }
    }
}

impl MenuTree {
    /// Creates a new, empty tree.
    pub fn new() -> Self {
        Self::default()
    }

    /// Remove every children from this tree.
    pub fn clear(&mut self) {
        self.children.clear();
    }

    /// Inserts an item at the given position.
    pub fn insert(&mut self, i: usize, item: MenuItem) {
        self.children.insert(i, item);
    }

    /// Inserts a delimiter at the given position.
    pub fn insert_delimiter(&mut self, i: usize) {
        self.insert(i, MenuItem::Delimiter);
    }

    /// Adds a delimiter to the end of this tree.
    pub fn add_delimiter(&mut self) {
        let i = self.children.len();
        self.insert_delimiter(i);
    }

    /// Adds a delimiter to the end of this tree - chainable variant.
    pub fn delimiter(self) -> Self {
        self.with(|menu| menu.add_delimiter())
    }

    /// Adds a actionnable leaf to the end of this tree.
    pub fn add_leaf<S, F>(&mut self, title: S, cb: F)
    where
        S: Into<String>,
        F: 'static + Fn(&mut Cursive),
    {
        let i = self.children.len();
        self.insert_leaf(i, title, cb);
    }

    /// Inserts a leaf at the given position.
    pub fn insert_leaf<S, F>(&mut self, i: usize, title: S, cb: F)
    where
        S: Into<String>,
        F: 'static + Fn(&mut Cursive),
    {
        let title = title.into();
        self.insert(i, MenuItem::Leaf(title, Callback::from_fn(cb)));
    }

    /// Adds a actionnable leaf to the end of this tree - chainable variant.
    pub fn leaf<S, F>(self, title: S, cb: F) -> Self
    where
        S: Into<String>,
        F: 'static + Fn(&mut Cursive),
    {
        self.with(|menu| menu.add_leaf(title, cb))
    }

    /// Inserts a subtree at the given position.
    pub fn insert_subtree<S>(&mut self, i: usize, title: S, tree: MenuTree)
    where
        S: Into<String>,
    {
        let title = title.into();
        let tree = MenuItem::Subtree(title, Rc::new(tree));
        self.insert(i, tree);
    }

    /// Adds a submenu to the end of this tree.
    pub fn add_subtree<S>(&mut self, title: S, tree: MenuTree)
    where
        S: Into<String>,
    {
        let i = self.children.len();
        self.insert_subtree(i, title, tree);
    }

    /// Adds a submenu to the end of this tree - chainable variant.
    pub fn subtree<S>(self, title: S, tree: MenuTree) -> Self
    where
        S: Into<String>,
    {
        self.with(|menu| menu.add_subtree(title, tree))
    }

    /// Looks for the child at the given position.
    ///
    /// Returns `None` if `i >= self.len()`.
    pub fn get_mut(&mut self, i: usize) -> Option<&mut MenuItem> {
        self.children.get_mut(i)
    }

    /// Returns the item at the given position.
    ///
    /// Returns `None` if `i > self.len()` or if the item is not a subtree.
    pub fn get_subtree(&mut self, i: usize) -> Option<&mut MenuTree> {
        self.get_mut(i).and_then(MenuItem::as_subtree)
    }

    /// Looks for a child with the given title.
    ///
    /// Returns `None` if no such label was found.
    pub fn find_item(&mut self, title: &str) -> Option<&mut MenuItem> {
        self.children
            .iter_mut()
            .find(|child| child.label() == title)
    }

    /// Looks for a subtree with the given title.
    pub fn find_subtree(&mut self, title: &str) -> Option<&mut MenuTree> {
        self.children
            .iter_mut()
            .filter(|child| child.label() == title)
            .filter_map(MenuItem::as_subtree)
            .next()
    }

    /// Returns the position of a child with the given label.
    ///
    /// Returns `None` if no such label was found.
    pub fn find_position(&mut self, title: &str) -> Option<usize> {
        self.children
            .iter()
            .position(|child| child.label() == title)
    }

    /// Removes the item at the given position.
    pub fn remove(&mut self, i: usize) {
        self.children.remove(i);
    }

    /// Returns the number of direct children in this node.
    ///
    /// * Includes delimiters.
    /// * Does not count nested children.
    pub fn len(&self) -> usize {
        self.children.len()
    }

    /// Returns `true` if this tree has no children.
    pub fn is_empty(&self) -> bool {
        self.children.is_empty()
    }
}