cartographer_rs/menu.rs
1mod interact;
2
3#[cfg_attr(
4 feature = "serde_serialize",
5 derive(serde::Serialize, serde::Deserialize)
6)]
7#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
8/// A data structure representing a line-item in a menu
9///
10/// The recommended way of constructing these is to use the [`menu_item!`](crate::menu_item!) macro
11/// though the output will be the same
12///
13/// ## Example
14/// ```
15/// let menu_item = MenuItem::new("A Menu Item".to_string())
16/// .visible_at_rest(true)
17/// .at_rest_position(1);
18///
19/// // is the same as
20///
21/// menu_item!("A Menu Item", true, 1);
22/// ```
23pub struct MenuItem {
24 /// The String that will display for this item in the menu
25 visible_name: String,
26
27 /// Toggles if this item will be shown when no search terms are available
28 visible_at_rest: bool,
29
30 /// Optional feature that will let you specify in what order the MenuItems will be
31 /// displayed
32 at_rest_position: Option<usize>,
33
34 /// A list of strings that will also be used, in addition to the `visible_name`,
35 /// when processing the search results
36 alternative_matches: Option<Vec<String>>,
37}
38
39impl MenuItem {
40 /// Create a new MenuItem with the visible name specified
41 pub fn new(visible_name: String) -> Self {
42 MenuItem {
43 visible_name,
44 visible_at_rest: true,
45 at_rest_position: None,
46 alternative_matches: None,
47 }
48 }
49
50 /// Set whether a [`MenuItem`] is visible when no search is showing
51 pub fn visible_at_rest(self, visible: bool) -> Self {
52 MenuItem {
53 visible_at_rest: visible,
54 ..self
55 }
56 }
57
58 /// Set a [`MenuItem`]'s resting position in the no search menu
59 /// Note: Won't have any effect if visible_at_rest is false
60 pub fn at_rest_position(self, position: usize) -> Self {
61 MenuItem {
62 at_rest_position: Some(position),
63 ..self
64 }
65 }
66
67 /// Set alternative matches for a [`MenuItem`]. These are strings that this item will
68 /// match to when searching - in addition to the visible_name
69 pub fn add_alternative_match(self, new_matches: Vec<String>) -> Self {
70 let mut cur_matches: Vec<String>;
71 if self.alternative_matches.is_none() {
72 cur_matches = Vec::new();
73 } else {
74 cur_matches = self.alternative_matches.unwrap();
75 }
76 for i in new_matches {
77 cur_matches.push(i);
78 }
79 MenuItem {
80 alternative_matches: Some(cur_matches),
81 ..self
82 }
83 }
84}
85
86/// The Menu struct that contains the information and
87/// functions for displaying the menus
88#[derive(Clone, Debug, PartialEq)]
89pub struct Menu {
90 /// The text to be displayed on the same line as user input will be shown.
91 /// To make it extra clear, try adding a semicolon and a space. (e.g. `prompt: "Pick and item: "`)
92 prompt: String,
93
94 /// The Vector of [`MenuItem`]s
95 items: Vec<MenuItem>,
96
97 /// The [`MenuOptions`] to use when displaying the menu
98 configuration: MenuOptions,
99}
100
101impl Menu {
102 /// Create a new Menu from a prompt, list of [`MenuItem`](crate::MenuItem)s, and an optional
103 /// [`MenuOptions`](crate::MenuOptions) instance. If configuration is `None`, then the default
104 /// is used
105 pub fn new(
106 prompt: String,
107 menu_items: Vec<MenuItem>,
108 configuration: Option<MenuOptions>,
109 ) -> Menu {
110 Menu {
111 prompt,
112 items: menu_items,
113 configuration: {
114 if let Some(configuration) = configuration {
115 configuration
116 } else {
117 MenuOptions {
118 ..MenuOptions::default()
119 }
120 }
121 },
122 }
123 }
124}
125
126/// Controls and characters that can be configured
127/// to change the way the menu acts and displays
128///
129///
130/// ## Example
131/// ```
132/// let options = MenuOptions::new()
133/// .cursor("→")
134/// .select_key(console::Key::Tab)
135/// .max_lines_visible(6);
136///
137/// menu!("Only 6 lines are visible!",
138/// menu_items_list,
139/// options
140/// )
141///
142/// ```
143///
144#[derive(Clone, Debug, PartialEq)]
145pub struct MenuOptions {
146 /// The user's cursor while they navigate
147 cursor: String,
148 cursor_width: usize,
149
150 /// The char used to indicate an item is selected
151 selected_indicator: String,
152 selected_indicator_width: usize,
153
154 /// The button the user uses to select an item
155 select_key: console::Key,
156
157 /// The maximum number of vertical lines the menu can have
158 max_lines_visible: usize,
159
160 /// The minimum search score for an item to be displayed in the menu
161 /// The lower the number, the more results will be displayed
162 min_search_threshold: f32,
163
164 /// Configures if selected items stay visible in search results
165 show_select_in_search: bool,
166
167 /// Set if the menu returns the first selected item
168 only_one: bool,
169
170 /// Set if the menu cleans up the terminal after exiting
171 clear_menu_on_exit: bool,
172}
173
174impl MenuOptions {
175 /// Create a new [`MenuOptions`] with all the defaults
176 pub fn new() -> Self {
177 MenuOptions {
178 ..MenuOptions::default()
179 }
180 }
181
182 /// Set the user's row-indicator/cursor to a custom character.
183 /// The default is: '>'
184 pub fn cursor(self, cursor: &str) -> Self {
185 MenuOptions { cursor: cursor.to_string(), ..self }
186 }
187 /// Override the space given for the cursor navigation column
188 pub fn cursor_width(self, cursor_width: usize) -> Self {
189 MenuOptions { cursor_width, ..self }
190 }
191
192 /// Set the "Item Selected" indicator to a custom character.
193 /// Defaults is: 'X'
194 pub fn selected_indicator(self, indicator: &str) -> Self {
195 MenuOptions {
196 selected_indicator: indicator.to_string(),
197 ..self
198 }
199 }
200 /// Override the space given for the cursor navigation column
201 pub fn selected_indicator_width(self, indicator_width: usize) -> Self {
202 MenuOptions { selected_indicator_width: indicator_width, ..self }
203 }
204 /// Set the key that is used to select an item.
205 /// The default is: [`console::Key::Char(' ')`]
206 pub fn select_key(self, key: console::Key) -> Self {
207 MenuOptions {
208 select_key: key,
209 ..self
210 }
211 }
212 /// Set the maximum number of items that will be displayed at any one time.
213 /// The default is: 10
214 pub fn max_lines_visible(self, max_lines: usize) -> Self {
215 MenuOptions {
216 max_lines_visible: max_lines,
217 ..self
218 }
219 }
220 /// Set the degree of "fuzziness" that it will match too. Higher numbers will return more
221 /// results, but less accurate ones. Has to be 1.0 >= x >= 0 or will panic
222 /// The default is: 0.005
223 pub fn minimum_search_threshold(self, threshold: f32) -> Self {
224 assert!( (0.0..1.0).contains(&threshold) );
225 MenuOptions {
226 min_search_threshold: threshold,
227 ..self
228 }
229 }
230 /// Set if 'selected' rows are still shown during searches they aren't results for
231 /// The default is: true
232 pub fn show_selected_in_search(self, show_in_search: bool) -> Self {
233 MenuOptions {
234 show_select_in_search: show_in_search,
235 ..self
236 }
237 }
238 /// Set if the menu should exit and return only the first user selection.
239 /// The default is: false
240 pub fn only_one_selection(self, only_one: bool) -> Self {
241 MenuOptions { only_one, ..self }
242 }
243 /// Set if the menu should delete any left-over lines from the terminal.
244 /// The default is: true
245 pub fn clear_on_close(self, do_clear: bool) -> Self {
246 MenuOptions {
247 clear_menu_on_exit: do_clear,
248 ..self
249 }
250 }
251}
252
253impl Default for MenuOptions {
254 fn default() -> Self {
255 MenuOptions {
256 cursor: ">".to_string(),
257 cursor_width: 1,
258 selected_indicator: "X".to_string(),
259 selected_indicator_width: 1,
260 select_key: console::Key::Char(' '),
261 max_lines_visible: 10,
262 min_search_threshold: 0.005,
263 show_select_in_search: true,
264 only_one: false,
265 clear_menu_on_exit: true,
266 }
267 }
268}