rat_menu/
popup_menu.rs

1//! Renders a popup menu.
2//!
3//! The popup menu ignores the area you give to render() completely.
4//! Instead, you need to call [constraint](PopupMenu::constraint)
5//! to give some constraints where the popup-menu should be rendered.
6//! You can also set an outer [boundary](PopupMenu::boundary) to limit
7//! the area. The size of the popup menu is fully determined by the
8//! menu-items.
9//!
10//! ```
11//! use ratatui::buffer::Buffer;
12//! use ratatui::layout::{Alignment, Rect};
13//! use ratatui::widgets::{Block, StatefulWidget};
14//! use rat_menu::menuitem::Separator;
15//! use rat_menu::popup_menu::{PopupMenu, PopupMenuState};
16//! use rat_popup::PopupConstraint;
17//!
18//! # struct State { popup: PopupMenuState }
19//! # let mut state = State { popup: Default::default() };
20//! # let mut buf = Buffer::default();
21//! # let buf = &mut buf;
22//! # let widget_area = Rect::default();
23//!
24//! PopupMenu::new()
25//!     .item_parsed("Item _1")
26//!     .separator(Separator::Plain)
27//!     .item_parsed("Item _2")
28//!     .item_parsed("Item _3")
29//!     .block(Block::bordered())
30//!     .constraint(
31//!         PopupConstraint::Above(Alignment::Left, widget_area)
32//!     )
33//!     .render(Rect::default(), buf, &mut state.popup);
34//! ```
35//!
36use crate::_private::NonExhaustive;
37use crate::event::MenuOutcome;
38use crate::util::{get_block_padding, get_block_size, revert_style};
39use crate::{MenuBuilder, MenuItem, MenuStyle, Separator};
40use rat_event::util::{MouseFlags, mouse_trap};
41use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Popup, ct_event};
42pub use rat_popup::PopupConstraint;
43use rat_popup::event::PopupOutcome;
44use rat_popup::{PopupCore, PopupCoreState};
45use ratatui::buffer::Buffer;
46use ratatui::layout::{Rect, Size};
47use ratatui::prelude::BlockExt;
48use ratatui::style::{Style, Stylize};
49use ratatui::text::{Line, Span};
50use ratatui::widgets::StatefulWidget;
51use ratatui::widgets::{Block, Padding, Widget};
52use std::cmp::max;
53use unicode_segmentation::UnicodeSegmentation;
54
55/// Popup menu.
56#[derive(Debug, Default, Clone)]
57pub struct PopupMenu<'a> {
58    pub(crate) menu: MenuBuilder<'a>,
59
60    width: Option<u16>,
61    popup: PopupCore<'a>,
62    block: Option<Block<'a>>,
63    style: Style,
64    highlight_style: Option<Style>,
65    disabled_style: Option<Style>,
66    right_style: Option<Style>,
67    focus_style: Option<Style>,
68}
69
70/// State & event handling.
71#[derive(Debug, Clone)]
72pub struct PopupMenuState {
73    /// Popup data.
74    pub popup: PopupCoreState,
75    /// Areas for each item.
76    /// __readonly__. renewed for each render.
77    pub item_areas: Vec<Rect>,
78    /// Area for the separator after each item.
79    /// The area has height 0 if there is no separator.
80    /// __readonly__. renewed for each render.
81    pub sep_areas: Vec<Rect>,
82    /// Letter navigation
83    /// __readonly__. renewed for each render.
84    pub navchar: Vec<Option<char>>,
85    /// Disabled menu-items.
86    pub disabled: Vec<bool>,
87    /// Selected item.
88    /// __read+write__
89    pub selected: Option<usize>,
90
91    /// Mouse flags
92    /// __used for mouse interaction__
93    pub mouse: MouseFlags,
94
95    pub non_exhaustive: NonExhaustive,
96}
97
98impl Default for PopupMenuState {
99    fn default() -> Self {
100        Self {
101            popup: Default::default(),
102            item_areas: Default::default(),
103            sep_areas: Default::default(),
104            navchar: Default::default(),
105            disabled: Default::default(),
106            selected: Default::default(),
107            mouse: Default::default(),
108            non_exhaustive: NonExhaustive,
109        }
110    }
111}
112
113impl PopupMenu<'_> {
114    fn size(&self) -> Size {
115        let width = if let Some(width) = self.width {
116            width
117        } else {
118            let text_width = self
119                .menu
120                .items
121                .iter()
122                .map(|v| (v.item_width() * 3) / 2 + v.right_width())
123                .max();
124            text_width.unwrap_or(10)
125        };
126        let height = self.menu.items.iter().map(MenuItem::height).sum::<u16>();
127
128        let block = get_block_size(&self.block);
129
130        #[allow(clippy::if_same_then_else)]
131        let vertical_padding = if block.height == 0 { 2 } else { 0 };
132        let horizontal_padding = 2;
133
134        Size::new(
135            width + horizontal_padding + block.width,
136            height + vertical_padding + block.height,
137        )
138    }
139
140    fn layout(&self, area: Rect, state: &mut PopupMenuState) {
141        let block = get_block_size(&self.block);
142        let inner = self.block.inner_if_some(area);
143
144        // add text padding.
145        #[allow(clippy::if_same_then_else)]
146        let vert_offset = if block.height == 0 { 1 } else { 0 };
147        let horiz_offset = 1;
148        let horiz_offset_sep = 0;
149
150        state.item_areas.clear();
151        state.sep_areas.clear();
152
153        let mut row = 0;
154
155        for item in &self.menu.items {
156            state.item_areas.push(Rect::new(
157                inner.x + horiz_offset,
158                inner.y + row + vert_offset,
159                inner.width.saturating_sub(2 * horiz_offset),
160                1,
161            ));
162            state.sep_areas.push(Rect::new(
163                inner.x + horiz_offset_sep,
164                inner.y + row + 1 + vert_offset,
165                inner.width.saturating_sub(2 * horiz_offset_sep),
166                if item.separator.is_some() { 1 } else { 0 },
167            ));
168
169            row += item.height();
170        }
171    }
172}
173
174impl<'a> PopupMenu<'a> {
175    /// New, empty.
176    pub fn new() -> Self {
177        Default::default()
178    }
179
180    /// Add an item.
181    pub fn item(mut self, item: MenuItem<'a>) -> Self {
182        self.menu.item(item);
183        self
184    }
185
186    /// Parse the text.
187    ///
188    /// __See__
189    ///
190    /// [MenuItem::new_parsed]
191    pub fn item_parsed(mut self, text: &'a str) -> Self {
192        self.menu.item_parsed(text);
193        self
194    }
195
196    /// Add a text-item.
197    pub fn item_str(mut self, txt: &'a str) -> Self {
198        self.menu.item_str(txt);
199        self
200    }
201
202    /// Add an owned text as item.
203    pub fn item_string(mut self, txt: String) -> Self {
204        self.menu.item_string(txt);
205        self
206    }
207
208    /// Sets the separator for the last item added.
209    /// If there is none adds this as an empty menu-item.
210    pub fn separator(mut self, separator: Separator) -> Self {
211        self.menu.separator(separator);
212        self
213    }
214
215    /// Fixed width for the menu.
216    /// If not set it uses 1.5 times the length of the longest item.
217    pub fn width(mut self, width: u16) -> Self {
218        self.width = Some(width);
219        self
220    }
221
222    /// Fixed width for the menu.
223    /// If not set it uses 1.5 times the length of the longest item.
224    pub fn width_opt(mut self, width: Option<u16>) -> Self {
225        self.width = width;
226        self
227    }
228
229    /// Set relative placement.
230    pub fn constraint(mut self, placement: PopupConstraint) -> Self {
231        self.popup = self.popup.constraint(placement);
232        self
233    }
234
235    /// Adds an extra offset.
236    pub fn offset(mut self, offset: (i16, i16)) -> Self {
237        self.popup = self.popup.offset(offset);
238        self
239    }
240
241    /// Adds an extra x offset.
242    pub fn x_offset(mut self, offset: i16) -> Self {
243        self.popup = self.popup.x_offset(offset);
244        self
245    }
246
247    /// Adds an extra y offset.
248    pub fn y_offset(mut self, offset: i16) -> Self {
249        self.popup = self.popup.y_offset(offset);
250        self
251    }
252
253    /// Set outer bounds for the popup-menu.
254    /// If not set, the [Buffer::area] is used as outer bounds.
255    pub fn boundary(mut self, boundary: Rect) -> Self {
256        self.popup = self.popup.boundary(boundary);
257        self
258    }
259
260    /// Set a style-set.
261    pub fn styles(mut self, styles: MenuStyle) -> Self {
262        self.popup = self.popup.styles(styles.popup.clone());
263
264        #[allow(deprecated)]
265        if let Some(popup_style) = styles.popup_style {
266            self.style = popup_style;
267        } else if styles.popup.style != Style::default() {
268            self.style = styles.popup.style;
269        } else {
270            self.style = styles.style;
271        }
272
273        self.block = self.block.map(|v| v.style(self.style));
274        #[allow(deprecated)]
275        if let Some(border_style) = styles.popup_border {
276            self.block = self.block.map(|v| v.border_style(border_style));
277        } else if let Some(border_style) = styles.popup.border_style {
278            self.block = self.block.map(|v| v.border_style(border_style));
279        }
280        #[allow(deprecated)]
281        if styles.block.is_some() {
282            self.block = styles.block;
283        } else if styles.popup.block.is_some() {
284            self.block = styles.popup.block;
285        }
286
287        if styles.highlight.is_some() {
288            self.highlight_style = styles.highlight;
289        }
290        if styles.disabled.is_some() {
291            self.disabled_style = styles.disabled;
292        }
293        if styles.right.is_some() {
294            self.right_style = styles.right;
295        }
296        if styles.focus.is_some() {
297            self.focus_style = styles.focus;
298        }
299        self
300    }
301
302    /// Base style.
303    pub fn style(mut self, style: Style) -> Self {
304        self.style = style;
305        self.block = self.block.map(|v| v.style(self.style));
306        self
307    }
308
309    /// Highlight style.
310    pub fn highlight_style(mut self, style: Style) -> Self {
311        self.highlight_style = Some(style);
312        self
313    }
314
315    /// Highlight style.
316    pub fn highlight_style_opt(mut self, style: Option<Style>) -> Self {
317        self.highlight_style = style;
318        self
319    }
320
321    /// Disabled item style.
322    #[inline]
323    pub fn disabled_style(mut self, style: Style) -> Self {
324        self.disabled_style = Some(style);
325        self
326    }
327
328    /// Disabled item style.
329    #[inline]
330    pub fn disabled_style_opt(mut self, style: Option<Style>) -> Self {
331        self.disabled_style = style;
332        self
333    }
334
335    /// Style for the hotkey.
336    #[inline]
337    pub fn right_style(mut self, style: Style) -> Self {
338        self.right_style = Some(style);
339        self
340    }
341
342    /// Style for the hotkey.
343    #[inline]
344    pub fn right_style_opt(mut self, style: Option<Style>) -> Self {
345        self.right_style = style;
346        self
347    }
348
349    /// Focus/Selection style.
350    pub fn focus_style(mut self, style: Style) -> Self {
351        self.focus_style = Some(style);
352        self
353    }
354
355    /// Focus/Selection style.
356    pub fn focus_style_opt(mut self, style: Option<Style>) -> Self {
357        self.focus_style = style;
358        self
359    }
360
361    /// Block for borders.
362    pub fn block(mut self, block: Block<'a>) -> Self {
363        self.block = Some(block);
364        self.block = self.block.map(|v| v.style(self.style));
365        self
366    }
367
368    /// Block for borders.
369    pub fn block_opt(mut self, block: Option<Block<'a>>) -> Self {
370        self.block = block;
371        self.block = self.block.map(|v| v.style(self.style));
372        self
373    }
374
375    /// Get the padding the block imposes as a Size.
376    pub fn get_block_size(&self) -> Size {
377        get_block_size(&self.block)
378    }
379
380    /// Get the padding the block imposes as Padding.
381    pub fn get_block_padding(&self) -> Padding {
382        get_block_padding(&self.block)
383    }
384}
385
386impl<'a> StatefulWidget for &PopupMenu<'a> {
387    type State = PopupMenuState;
388
389    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
390        render_popup_menu(self, area, buf, state);
391    }
392}
393
394impl StatefulWidget for PopupMenu<'_> {
395    type State = PopupMenuState;
396
397    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
398        render_popup_menu(&self, area, buf, state);
399    }
400}
401
402fn render_popup_menu(
403    widget: &PopupMenu<'_>,
404    _area: Rect,
405    buf: &mut Buffer,
406    state: &mut PopupMenuState,
407) {
408    if widget.menu.items.is_empty() {
409        state.selected = None;
410    } else if state.selected.is_none() {
411        state.selected = Some(0);
412    }
413
414    state.navchar = widget.menu.items.iter().map(|v| v.navchar).collect();
415    state.disabled = widget.menu.items.iter().map(|v| v.disabled).collect();
416
417    if !state.is_active() {
418        state.clear_areas();
419        return;
420    }
421
422    let size = widget.size();
423    let area = Rect::new(0, 0, size.width, size.height);
424
425    (&widget.popup).render(area, buf, &mut state.popup);
426    if widget.block.is_some() {
427        widget.block.render(state.popup.area, buf);
428    } else {
429        buf.set_style(state.popup.area, widget.style);
430    }
431    widget.layout(state.popup.area, state);
432    render_items(widget, buf, state);
433}
434
435fn render_items(widget: &PopupMenu<'_>, buf: &mut Buffer, state: &mut PopupMenuState) {
436    let focus_style = widget.focus_style.unwrap_or(revert_style(widget.style));
437
438    let style = widget.style;
439    let right_style = style.patch(widget.right_style.unwrap_or_default());
440    let highlight_style = style.patch(widget.highlight_style.unwrap_or(Style::new().underlined()));
441    let disabled_style = style.patch(widget.disabled_style.unwrap_or_default());
442
443    let sel_style = focus_style;
444    let sel_right_style = focus_style.patch(widget.right_style.unwrap_or_default());
445    let sel_highlight_style = focus_style;
446    let sel_disabled_style = focus_style.patch(widget.disabled_style.unwrap_or_default());
447
448    for (n, item) in widget.menu.items.iter().enumerate() {
449        let mut item_area = state.item_areas[n];
450
451        #[allow(clippy::collapsible_else_if)]
452        let (style, right_style, highlight_style) = if state.selected == Some(n) {
453            if item.disabled {
454                (sel_disabled_style, sel_right_style, sel_highlight_style)
455            } else {
456                (sel_style, sel_right_style, sel_highlight_style)
457            }
458        } else {
459            if item.disabled {
460                (disabled_style, right_style, highlight_style)
461            } else {
462                (style, right_style, highlight_style)
463            }
464        };
465
466        let item_line = if let Some(highlight) = item.highlight.clone() {
467            Line::from_iter([
468                Span::from(&item.item[..highlight.start - 1]), // account for _
469                Span::from(&item.item[highlight.start..highlight.end]).style(highlight_style),
470                Span::from(&item.item[highlight.end..]),
471            ])
472        } else {
473            Line::from(item.item.as_ref())
474        };
475        item_line.style(style).render(item_area, buf);
476
477        if !item.right.is_empty() {
478            let right_width = item.right.graphemes(true).count() as u16;
479            if right_width < item_area.width {
480                let delta = item_area.width.saturating_sub(right_width);
481                item_area.x += delta;
482                item_area.width -= delta;
483            }
484            Span::from(item.right.as_ref())
485                .style(right_style)
486                .render(item_area, buf);
487        }
488
489        if let Some(separator) = item.separator {
490            let sep_area = state.sep_areas[n];
491            let sym = match separator {
492                Separator::Empty => " ",
493                Separator::Plain => "\u{2500}",
494                Separator::Thick => "\u{2501}",
495                Separator::Double => "\u{2550}",
496                Separator::Dashed => "\u{2212}",
497                Separator::Dotted => "\u{2508}",
498            };
499            for x in 0..sep_area.width {
500                if let Some(cell) = buf.cell_mut((sep_area.x + x, sep_area.y)) {
501                    cell.set_symbol(sym);
502                }
503            }
504        }
505    }
506}
507
508impl PopupMenuState {
509    /// New
510    #[inline]
511    pub fn new() -> Self {
512        Default::default()
513    }
514
515    /// New state with a focus name.
516    #[deprecated(since = "1.1.0", note = "no longer useful")]
517    pub fn named(_: &'static str) -> Self {
518        Self {
519            popup: PopupCoreState::new(),
520            ..Default::default()
521        }
522    }
523
524    /// Set the z-index for the popup-menu.
525    pub fn set_popup_z(&mut self, z: u16) {
526        self.popup.area_z = z;
527    }
528
529    /// The z-index for the popup-menu.
530    pub fn popup_z(&self) -> u16 {
531        self.popup.area_z
532    }
533
534    /// Show the popup.
535    pub fn flip_active(&mut self) {
536        self.popup.flip_active();
537    }
538
539    /// Show the popup.
540    pub fn is_active(&self) -> bool {
541        self.popup.is_active()
542    }
543
544    /// Show the popup.
545    pub fn set_active(&mut self, active: bool) {
546        self.popup.set_active(active);
547        if !active {
548            self.clear_areas();
549        }
550    }
551
552    /// Clear the areas.
553    pub fn clear_areas(&mut self) {
554        self.popup.clear_areas();
555        self.sep_areas.clear();
556        self.navchar.clear();
557        self.item_areas.clear();
558        self.disabled.clear();
559    }
560
561    /// Number of items.
562    #[inline]
563    pub fn len(&self) -> usize {
564        self.item_areas.len()
565    }
566
567    /// Any items.
568    #[inline]
569    pub fn is_empty(&self) -> bool {
570        self.item_areas.is_empty()
571    }
572
573    /// Selected item.
574    #[inline]
575    pub fn select(&mut self, select: Option<usize>) -> bool {
576        let old = self.selected;
577        self.selected = select;
578        old != self.selected
579    }
580
581    /// Selected item.
582    #[inline]
583    pub fn selected(&self) -> Option<usize> {
584        self.selected
585    }
586
587    /// Select the previous item.
588    #[inline]
589    pub fn prev_item(&mut self) -> bool {
590        let old = self.selected;
591
592        // before first render or no items:
593        if self.disabled.is_empty() {
594            return false;
595        }
596
597        self.selected = if let Some(start) = old {
598            let mut idx = start;
599            loop {
600                if idx == 0 {
601                    idx = start;
602                    break;
603                }
604                idx -= 1;
605
606                if self.disabled.get(idx) == Some(&false) {
607                    break;
608                }
609            }
610            Some(idx)
611        } else if !self.is_empty() {
612            Some(self.len() - 1)
613        } else {
614            None
615        };
616
617        old != self.selected
618    }
619
620    /// Select the next item.
621    #[inline]
622    pub fn next_item(&mut self) -> bool {
623        let old = self.selected;
624
625        // before first render or no items:
626        if self.disabled.is_empty() {
627            return false;
628        }
629
630        self.selected = if let Some(start) = old {
631            let mut idx = start;
632            loop {
633                if idx + 1 == self.len() {
634                    idx = start;
635                    break;
636                }
637                idx += 1;
638
639                if self.disabled.get(idx) == Some(&false) {
640                    break;
641                }
642            }
643            Some(idx)
644        } else if !self.is_empty() {
645            Some(0)
646        } else {
647            None
648        };
649
650        old != self.selected
651    }
652
653    /// Select by navigation key.
654    #[inline]
655    pub fn navigate(&mut self, c: char) -> MenuOutcome {
656        // before first render or no items:
657        if self.disabled.is_empty() {
658            return MenuOutcome::Continue;
659        }
660
661        let c = c.to_ascii_lowercase();
662        for (i, cc) in self.navchar.iter().enumerate() {
663            #[allow(clippy::collapsible_if)]
664            if *cc == Some(c) {
665                if self.disabled.get(i) == Some(&false) {
666                    if self.selected == Some(i) {
667                        return MenuOutcome::Activated(i);
668                    } else {
669                        self.selected = Some(i);
670                        return MenuOutcome::Selected(i);
671                    }
672                }
673            }
674        }
675
676        MenuOutcome::Continue
677    }
678
679    /// Select item at position.
680    #[inline]
681    #[allow(clippy::collapsible_if)]
682    pub fn select_at(&mut self, pos: (u16, u16)) -> bool {
683        let old_selected = self.selected;
684
685        // before first render or no items:
686        if self.disabled.is_empty() {
687            return false;
688        }
689
690        if let Some(idx) = self.mouse.item_at(&self.item_areas, pos.0, pos.1) {
691            if !self.disabled[idx] {
692                self.selected = Some(idx);
693            }
694        }
695
696        self.selected != old_selected
697    }
698
699    /// Item at position.
700    #[inline]
701    pub fn item_at(&self, pos: (u16, u16)) -> Option<usize> {
702        self.mouse.item_at(&self.item_areas, pos.0, pos.1)
703    }
704}
705
706impl HandleEvent<crossterm::event::Event, Popup, MenuOutcome> for PopupMenuState {
707    /// Handle all events.
708    ///
709    /// Key-events are only handled when the popup menu
710    /// [is_active](PopupMenuState::is_active).
711    /// You need to set this state according to your logic.
712    ///
713    /// The popup menu will return MenuOutcome::Hide if it
714    /// thinks it should be hidden.
715    ///
716    fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Popup) -> MenuOutcome {
717        let r0 = match self.popup.handle(event, Popup) {
718            PopupOutcome::Hide => MenuOutcome::Hide,
719            r => r.into(),
720        };
721
722        let r1 = if self.is_active() {
723            match event {
724                ct_event!(key press ANY-c) => {
725                    let r = self.navigate(*c);
726                    if matches!(r, MenuOutcome::Activated(_)) {
727                        self.set_active(false);
728                    }
729                    r
730                }
731                ct_event!(keycode press Up) => {
732                    if self.prev_item() {
733                        if let Some(selected) = self.selected {
734                            MenuOutcome::Selected(selected)
735                        } else {
736                            MenuOutcome::Changed
737                        }
738                    } else {
739                        MenuOutcome::Continue
740                    }
741                }
742                ct_event!(keycode press Down) => {
743                    if self.next_item() {
744                        if let Some(selected) = self.selected {
745                            MenuOutcome::Selected(selected)
746                        } else {
747                            MenuOutcome::Changed
748                        }
749                    } else {
750                        MenuOutcome::Continue
751                    }
752                }
753                ct_event!(keycode press Home) => {
754                    if self.select(Some(0)) {
755                        if let Some(selected) = self.selected {
756                            MenuOutcome::Selected(selected)
757                        } else {
758                            MenuOutcome::Changed
759                        }
760                    } else {
761                        MenuOutcome::Continue
762                    }
763                }
764                ct_event!(keycode press End) => {
765                    if self.select(Some(self.len().saturating_sub(1))) {
766                        if let Some(selected) = self.selected {
767                            MenuOutcome::Selected(selected)
768                        } else {
769                            MenuOutcome::Changed
770                        }
771                    } else {
772                        MenuOutcome::Continue
773                    }
774                }
775                ct_event!(keycode press Esc) => {
776                    self.set_active(false);
777                    MenuOutcome::Changed
778                }
779                ct_event!(keycode press Enter) => {
780                    if let Some(select) = self.selected {
781                        self.set_active(false);
782                        MenuOutcome::Activated(select)
783                    } else {
784                        MenuOutcome::Continue
785                    }
786                }
787
788                _ => MenuOutcome::Continue,
789            }
790        } else {
791            MenuOutcome::Continue
792        };
793
794        let r = max(r0, r1);
795
796        if !r.is_consumed() {
797            self.handle(event, MouseOnly)
798        } else {
799            r
800        }
801    }
802}
803
804impl HandleEvent<crossterm::event::Event, MouseOnly, MenuOutcome> for PopupMenuState {
805    fn handle(&mut self, event: &crossterm::event::Event, _: MouseOnly) -> MenuOutcome {
806        if self.is_active() {
807            let r = match event {
808                ct_event!(mouse moved for col, row)
809                    if self.popup.area.contains((*col, *row).into()) =>
810                {
811                    if self.select_at((*col, *row)) {
812                        MenuOutcome::Selected(self.selected().expect("selection"))
813                    } else {
814                        MenuOutcome::Unchanged
815                    }
816                }
817                ct_event!(mouse down Left for col, row)
818                    if self.popup.area.contains((*col, *row).into()) =>
819                {
820                    if self.item_at((*col, *row)).is_some() {
821                        self.set_active(false);
822                        MenuOutcome::Activated(self.selected().expect("selection"))
823                    } else {
824                        MenuOutcome::Unchanged
825                    }
826                }
827                _ => MenuOutcome::Continue,
828            };
829
830            r.or_else(|| mouse_trap(event, self.popup.area).into())
831        } else {
832            MenuOutcome::Continue
833        }
834    }
835}
836
837/// Handle all events.
838///
839/// Key-events are only handled when the popup menu
840/// [is_active](PopupMenuState::is_active).
841/// You need to set this state according to your logic.
842///
843/// The popup menu will return MenuOutcome::Hide if it
844/// thinks it should be hidden.
845///
846pub fn handle_popup_events(
847    state: &mut PopupMenuState,
848    event: &crossterm::event::Event,
849) -> MenuOutcome {
850    state.handle(event, Popup)
851}
852
853/// Handle only mouse-events.
854pub fn handle_mouse_events(
855    state: &mut PopupMenuState,
856    event: &crossterm::event::Event,
857) -> MenuOutcome {
858    state.handle(event, MouseOnly)
859}