From d09689fdd21d744b084d73d8a9a457d68bb75d38 Mon Sep 17 00:00:00 2001 From: shuo Date: Sat, 3 Aug 2024 20:07:01 +0800 Subject: [PATCH 1/7] add a nested group example, this demos how to handle deeply nested menu structure. Also align dropdown's x with menu bar --- examples/nested_group.rs | 146 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 20 ++++-- 2 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 examples/nested_group.rs diff --git a/examples/nested_group.rs b/examples/nested_group.rs new file mode 100644 index 0000000..eb555c9 --- /dev/null +++ b/examples/nested_group.rs @@ -0,0 +1,146 @@ +use color_eyre::config::HookBuilder; +use crossterm::{ + event::{self, Event, KeyCode}, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use ratatui::{ + prelude::*, + widgets::{Block, Paragraph}, +}; +use std::io::{self, stdout, Stdout}; +use tui_menu::{Menu, MenuEvent, MenuItem, MenuState}; + +fn main() -> color_eyre::Result<()> { + let mut terminal = init_terminal()?; + App::new().run(&mut terminal)?; + restore_terminal()?; + Ok(()) +} + +/// Install panic and error hooks that restore the terminal before printing the error. +pub fn init_hooks() -> color_eyre::Result<()> { + let (panic, error) = HookBuilder::default().into_hooks(); + let panic = panic.into_panic_hook(); + let error = error.into_eyre_hook(); + + std::panic::set_hook(Box::new(move |info| { + let _ = restore_terminal(); // ignore failure to restore terminal + panic(info); + })); + color_eyre::eyre::set_hook(Box::new(move |e| { + let _ = restore_terminal(); // ignore failure to restore terminal + error(e) + }))?; + + Ok(()) +} + +fn init_terminal() -> io::Result>> { + enable_raw_mode()?; + execute!(stdout(), EnterAlternateScreen)?; + Terminal::new(CrosstermBackend::new(stdout())) +} + +fn restore_terminal() -> io::Result<()> { + disable_raw_mode()?; + execute!(stdout(), LeaveAlternateScreen,) +} + +struct App { + content: String, + menu: MenuState, +} + +impl App { + fn new() -> Self { + Self { + content: String::new(), + menu: MenuState::new(vec![ + MenuItem::group( + "Group", + vec![ + MenuItem::group("Nested", vec![ + MenuItem::group("Nested 1", vec![ + MenuItem::group("Nested 2", vec![ + MenuItem::group("Nested 3", vec![ + MenuItem::group("Nested 4", vec![ + MenuItem::group("Nested 5", vec![ + MenuItem::group("Nested 6", vec![ + ]) + ]) + ]) + ]) + ]) + ]) + ]), + MenuItem::item("Exit", Action::Exit) + ], + ), + ]), + } + } +} + +#[derive(Debug, Clone)] +enum Action { + Exit, +} + +impl App { + fn run(mut self, terminal: &mut Terminal) -> io::Result<()> { + loop { + terminal.draw(|frame| frame.render_widget(&mut self, frame.size()))?; + + if event::poll(std::time::Duration::from_millis(10))? { + if let Event::Key(key) = event::read()? { + self.on_key_event(key); + } + } + + for e in self.menu.drain_events() { + match e { + MenuEvent::Selected(item) => match item { + Action::Exit => { + return Ok(()); + } + }, + } + self.menu.reset(); + } + } + } + + fn on_key_event(&mut self, key: event::KeyEvent) { + match key.code { + KeyCode::Char('h') | KeyCode::Left => self.menu.left(), + KeyCode::Char('l') | KeyCode::Right => self.menu.right(), + KeyCode::Char('j') | KeyCode::Down => self.menu.down(), + KeyCode::Char('k') | KeyCode::Up => self.menu.up(), + KeyCode::Esc => self.menu.reset(), + KeyCode::Enter => self.menu.select(), + _ => {} + } + } +} + +impl Widget for &mut App { + fn render(self, area: Rect, buf: &mut Buffer) { + use Constraint::*; + let [top, main] = Layout::vertical([Length(1), Fill(1)]).areas(area); + + Paragraph::new(self.content.as_str()) + .block(Block::bordered().title("Content").on_black()) + .render(main, buf); + + let [log, menu] = Layout::horizontal([Length(10), Fill(1)]).areas(area); + "tui-menu" + .bold() + .blue() + .into_centered_line() + .render(log, buf); + + // draw menu last, so it renders on top of other content + Menu::new().render(menu, buf, &mut self.menu); + } +} diff --git a/src/lib.rs b/src/lib.rs index a9b455c..5a72e16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -523,7 +523,7 @@ impl Menu { self } - /// render a item group in drop down + /// render an item group in drop down fn render_drop_down( &self, x: u16, @@ -532,7 +532,15 @@ impl Menu { buf: &mut ratatui::buffer::Buffer, _depth: usize, ) { + let x = if x + self.drop_down_width < buf.area().right() { + // Shift drawing area back to visible rect + x + } else { + buf.area().width - self.drop_down_width + }; + let area = Rect::new(x, y, self.drop_down_width, group.len() as u16); + Clear.render(area, buf); buf.set_style(area, self.drop_down_style); @@ -579,7 +587,7 @@ impl StatefulWidget for Menu { fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer, state: &mut Self::State) { let mut spans = vec![]; - let mut x_pos = 0; + let mut x_pos = area.x; let y_pos = area.y; for (idx, item) in state.root_item.children.iter().enumerate() { @@ -593,19 +601,19 @@ impl StatefulWidget for Menu { let group_x_pos = x_pos; let span = Span::styled(item.name(), item_style); - x_pos += span.width(); + x_pos += span.width() as u16; spans.push(span); if has_children && is_highlight { - self.render_drop_down(group_x_pos as u16, y_pos + 1, &item.children, buf, 1); + self.render_drop_down(group_x_pos, y_pos + 1, &item.children, buf, 1); } if idx < state.root_item.children.len() - 1 { let span = Span::raw(" | "); - x_pos += span.width(); + x_pos += span.width() as u16; spans.push(span); } } - buf.set_line(area.x, area.y, &Line::from(spans), x_pos as u16); + buf.set_line(area.x, area.y, &Line::from(spans), x_pos); } } From bd0b360b7897c6ee8184bc8cbd6a27fc39c300a9 Mon Sep 17 00:00:00 2001 From: shuo Date: Sat, 3 Aug 2024 20:51:38 +0800 Subject: [PATCH 2/7] fmt --- examples/nested_group.rs | 47 +++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/examples/nested_group.rs b/examples/nested_group.rs index eb555c9..2b50fd0 100644 --- a/examples/nested_group.rs +++ b/examples/nested_group.rs @@ -56,28 +56,31 @@ impl App { fn new() -> Self { Self { content: String::new(), - menu: MenuState::new(vec![ - MenuItem::group( - "Group", - vec![ - MenuItem::group("Nested", vec![ - MenuItem::group("Nested 1", vec![ - MenuItem::group("Nested 2", vec![ - MenuItem::group("Nested 3", vec![ - MenuItem::group("Nested 4", vec![ - MenuItem::group("Nested 5", vec![ - MenuItem::group("Nested 6", vec![ - ]) - ]) - ]) - ]) - ]) - ]) - ]), - MenuItem::item("Exit", Action::Exit) - ], - ), - ]), + menu: MenuState::new(vec![MenuItem::group( + "Group", + vec![ + MenuItem::group( + "Nested", + vec![MenuItem::group( + "Nested 1", + vec![MenuItem::group( + "Nested 2", + vec![MenuItem::group( + "Nested 3", + vec![MenuItem::group( + "Nested 4", + vec![MenuItem::group( + "Nested 5", + vec![MenuItem::group("Nested 6", vec![])], + )], + )], + )], + )], + )], + ), + MenuItem::item("Exit", Action::Exit), + ], + )]), } } } From ef1c4ef622fb9963988ef6dcf79dab965a46f324 Mon Sep 17 00:00:00 2001 From: shuo Date: Sun, 4 Aug 2024 10:58:56 +0800 Subject: [PATCH 3/7] stack nested group is drawing area is not enough, fix crashes if drawing area is too small --- src/lib.rs | 51 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5a72e16..51fea64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -530,20 +530,36 @@ impl Menu { y: u16, group: &[MenuItem], buf: &mut ratatui::buffer::Buffer, - _depth: usize, + depth: usize, ) { - let x = if x + self.drop_down_width < buf.area().right() { - // Shift drawing area back to visible rect - x + let (x, y, drop_down_width) = if x + self.drop_down_width <= buf.area().right() { + // the drawing area is large enough + (x, y, self.drop_down_width) } else { - buf.area().width - self.drop_down_width + // the drawing area is not large enough, so we need to shift the rect + let w = if buf.area.right() >= x + self.drop_down_width { + self.drop_down_width + } else { + buf.area.width.min(self.drop_down_width) + }; + let y = if depth == 1 { + // Do not shift down for first layer + y + } else { + y + 1 + }; + (buf.area.right() - w, y, w) }; - let area = Rect::new(x, y, self.drop_down_width, group.len() as u16); + let area = Rect::new(x, y, drop_down_width, group.len() as u16); + + // clamp to ensure we draw in areas + let area = area.clamp(*buf.area()); Clear.render(area, buf); buf.set_style(area, self.drop_down_style); + let mut active_group: Option<_> = None; for (idx, item) in group.iter().enumerate() { let item_y = y + idx as u16; let is_active = item.is_highlight; @@ -559,20 +575,25 @@ impl Menu { self.default_item_style }, ), - self.drop_down_width, + drop_down_width, ); // show children if is_active && !item.children.is_empty() { - self.render_drop_down( - x + self.drop_down_width, - item_y, - &item.children, - buf, - _depth + 1, - ); + active_group = Some((x + drop_down_width, item_y, item)); } } + + // draw sub group at the end to ensure its content not shadowed + if let Some((x, y, item)) = active_group { + self.render_drop_down( + x, + y, + &item.children, + buf, + depth + 1, + ); + } } } @@ -586,6 +607,8 @@ impl StatefulWidget for Menu { type State = MenuState; fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer, state: &mut Self::State) { + let area = area.clamp(*buf.area()); + let mut spans = vec![]; let mut x_pos = area.x; let y_pos = area.y; From cbbc5cd9b59561bbabd25be2bb2558e52e694a18 Mon Sep 17 00:00:00 2001 From: shuo Date: Sun, 4 Aug 2024 11:02:15 +0800 Subject: [PATCH 4/7] fmt --- src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 51fea64..881e5f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -586,13 +586,7 @@ impl Menu { // draw sub group at the end to ensure its content not shadowed if let Some((x, y, item)) = active_group { - self.render_drop_down( - x, - y, - &item.children, - buf, - depth + 1, - ); + self.render_drop_down(x, y, &item.children, buf, depth + 1); } } } From b4c3c56869ca7fd43d444521a6d8bf9cd2ba0916 Mon Sep 17 00:00:00 2001 From: shuo Date: Sun, 4 Aug 2024 16:13:42 +0800 Subject: [PATCH 5/7] shift the menus to leave space if needed --- examples/nested_group.rs | 51 ++++++++++-------- src/lib.rs | 108 +++++++++++++++++++++++++-------------- 2 files changed, 99 insertions(+), 60 deletions(-) diff --git a/examples/nested_group.rs b/examples/nested_group.rs index 2b50fd0..39e594d 100644 --- a/examples/nested_group.rs +++ b/examples/nested_group.rs @@ -56,31 +56,40 @@ impl App { fn new() -> Self { Self { content: String::new(), - menu: MenuState::new(vec![MenuItem::group( - "Group", - vec![ - MenuItem::group( - "Nested", - vec![MenuItem::group( - "Nested 1", + menu: MenuState::new(vec![ + MenuItem::group( + "Group 1", + vec![ + MenuItem::group( + "Nested", vec![MenuItem::group( - "Nested 2", + "Nested 1", vec![MenuItem::group( - "Nested 3", - vec![MenuItem::group( - "Nested 4", - vec![MenuItem::group( - "Nested 5", - vec![MenuItem::group("Nested 6", vec![])], - )], - )], + "Nested 2", + vec![ + MenuItem::group( + "Nested 3", + vec![MenuItem::group( + "Nested 4", + vec![ + MenuItem::group( + "Nested 5", + vec![MenuItem::group("Nested 6", vec![])], + ), + MenuItem::item("Item 5", Action::Exit), + ], + )], + ), + MenuItem::item("Item 3a", Action::Exit), + ], )], )], - )], - ), - MenuItem::item("Exit", Action::Exit), - ], - )]), + ), + MenuItem::item("Exit", Action::Exit), + ], + ), + MenuItem::item("Exit", Action::Exit), + ]), } } } diff --git a/src/lib.rs b/src/lib.rs index 881e5f1..36a8b98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,15 +38,13 @@ impl MenuState { /// ]); /// ``` pub fn new(items: Vec>) -> Self { + let mut root_item = MenuItem::group("root", items); + // the root item marked as always highlight + // this makes highlight logic more consistent + root_item.is_highlight = true; + Self { - root_item: MenuItem { - name: "root".into(), - data: None, - children: items, - // the root item marked as always highlight - // this makes highlight logic more consistent - is_highlight: true, - }, + root_item, events: Default::default(), } } @@ -313,7 +311,7 @@ pub struct MenuItem { } impl MenuItem { - /// helper function to create an non group item. + /// helper function to create a non group item. pub fn item(name: impl Into>, data: T) -> Self { Self { name: name.into(), @@ -458,7 +456,7 @@ impl MenuItem { /// last but one layer in highlight fn highlight_last_but_one(&mut self) -> Option<&mut Self> { - // if self is not highlighted or there is no highlighed child, return None + // if self is not highlighted or there is no highlighted child, return None if !self.is_highlight || self.highlight_child_mut().is_none() { return None; } @@ -473,17 +471,40 @@ impl MenuItem { } Some(last_but_one) } + + /// get current render depth + /// NOTE: render depth is bottom up + fn render_depth(&self) -> u16 { + let mut child_depth = 0u16; + for child in self.children.iter() { + if child.is_highlight { + child_depth = child.render_depth(); + break; + } + } + + let self_depth = if self.is_highlight && !self.children.is_empty() { + // if self is a group, and it is highlighted, it's children will be shown, so + // the depth is 1 + 1 + } else { + // otherwise, either it is an item, or it's children not showed, treat the depth as 0 + 0 + }; + + child_depth + self_depth + } } -/// Widget focos on display/render +/// Widget focus on display/render pub struct Menu { - /// default item style + /// style for default item style default_item_style: Style, - /// style when item is highlighted + /// style for highlighted item highlight_item_style: Style, - /// width for drop down group panel + /// width for drop down panel drop_down_width: u16, - /// style for the drop down panel + /// style for drop down panel drop_down_style: Style, _priv: PhantomData, } @@ -530,26 +551,25 @@ impl Menu { y: u16, group: &[MenuItem], buf: &mut ratatui::buffer::Buffer, - depth: usize, ) { - let (x, y, drop_down_width) = if x + self.drop_down_width <= buf.area().right() { - // the drawing area is large enough - (x, y, self.drop_down_width) - } else { - // the drawing area is not large enough, so we need to shift the rect - let w = if buf.area.right() >= x + self.drop_down_width { - self.drop_down_width - } else { - buf.area.width.min(self.drop_down_width) - }; - let y = if depth == 1 { - // Do not shift down for first layer - y - } else { - y + 1 - }; - (buf.area.right() - w, y, w) - }; + // get the maximum render_depth for group + let render_depth = group + .iter() + .map(|item| item.render_depth()) + .max() + .unwrap_or_default(); + + // prevent calculation issue if canvas is narrow + let drop_down_width = self.drop_down_width.min(buf.area.width); + + // calculate the maximum x, leaving enough space for deeper items + // drawing area: + // | a | b | c | d | + // | .. | me | child_1 | child_of_child | nothing here | + // x_max is the x when d is 0 + let b_plus_c = (render_depth + 1) * drop_down_width; + let x_max = buf.area().right().saturating_sub(b_plus_c); + let x = x.min(x_max); let area = Rect::new(x, y, drop_down_width, group.len() as u16); @@ -557,6 +577,7 @@ impl Menu { let area = area.clamp(*buf.area()); Clear.render(area, buf); + buf.set_style(area, self.drop_down_style); let mut active_group: Option<_> = None; @@ -564,11 +585,21 @@ impl Menu { let item_y = y + idx as u16; let is_active = item.is_highlight; + let item_name = item.name(); + + // make style apply to whole line by make name whole line + let mut item_name = format!("{: '); + } + buf.set_span( x, item_y, &Span::styled( - item.name(), + item_name, if is_active { self.highlight_item_style } else { @@ -578,15 +609,14 @@ impl Menu { drop_down_width, ); - // show children if is_active && !item.children.is_empty() { active_group = Some((x + drop_down_width, item_y, item)); } } - // draw sub group at the end to ensure its content not shadowed + // draw at the end to ensure its content above all items in current level if let Some((x, y, item)) = active_group { - self.render_drop_down(x, y, &item.children, buf, depth + 1); + self.render_drop_down(x, y, &item.children, buf); } } } @@ -622,7 +652,7 @@ impl StatefulWidget for Menu { spans.push(span); if has_children && is_highlight { - self.render_drop_down(group_x_pos, y_pos + 1, &item.children, buf, 1); + self.render_drop_down(group_x_pos, y_pos + 1, &item.children, buf); } if idx < state.root_item.children.len() - 1 { From 16f344bb42208aae637451e809e2cef44fe34302 Mon Sep 17 00:00:00 2001 From: shuo Date: Mon, 5 Aug 2024 23:49:47 +0800 Subject: [PATCH 6/7] Make dropdown position more stable --- src/lib.rs | 212 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 176 insertions(+), 36 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 36a8b98..ffca803 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ impl MenuState { /// trigger up movement /// NOTE: this action tries to do intuitive movement, - /// which means logicially it is not consistent, e.g: + /// which means logically it is not consistent, e.g: /// case 1: /// group 1 group 2 group 3 /// > sub item 1 @@ -254,6 +254,33 @@ impl MenuState { depth } + /// How many dropdown to render, including preview + /// NOTE: If current group contains sub-group, in order to keep ui consistent, + /// even the sub-group not selected, its space is counted + fn dropdown_count(&self) -> u16 { + let mut node = &self.root_item; + let mut count = 0; + loop { + match node.highlight_child() { + None => { + return count; + } + Some(highlight_child) => { + if highlight_child.is_group() { + // highlighted child is a group, then it's children is previewed + count += 1; + } else if node.children.iter().any(|c| c.is_group()) { + // if highlighted item is not a group, but if sibling contains group + // in order to keep ui consistency, also count it + count += 1; + } + + node = highlight_child; + } + } + } + } + /// select current highlight item, if it has children /// then push pub fn select(&mut self) { @@ -344,6 +371,12 @@ impl MenuItem { } } + #[cfg(test)] + fn with_highlight(mut self, highlight: bool) -> Self { + self.is_highlight = highlight; + self + } + /// whether this item is group pub fn is_group(&self) -> bool { !self.children.is_empty() @@ -471,29 +504,6 @@ impl MenuItem { } Some(last_but_one) } - - /// get current render depth - /// NOTE: render depth is bottom up - fn render_depth(&self) -> u16 { - let mut child_depth = 0u16; - for child in self.children.iter() { - if child.is_highlight { - child_depth = child.render_depth(); - break; - } - } - - let self_depth = if self.is_highlight && !self.children.is_empty() { - // if self is a group, and it is highlighted, it's children will be shown, so - // the depth is 1 - 1 - } else { - // otherwise, either it is an item, or it's children not showed, treat the depth as 0 - 0 - }; - - child_depth + self_depth - } } /// Widget focus on display/render @@ -545,20 +555,14 @@ impl Menu { } /// render an item group in drop down - fn render_drop_down( + fn render_dropdown( &self, x: u16, y: u16, group: &[MenuItem], buf: &mut ratatui::buffer::Buffer, + dropdown_count_to_go: u16, // including current, it is not drawn yet ) { - // get the maximum render_depth for group - let render_depth = group - .iter() - .map(|item| item.render_depth()) - .max() - .unwrap_or_default(); - // prevent calculation issue if canvas is narrow let drop_down_width = self.drop_down_width.min(buf.area.width); @@ -567,8 +571,9 @@ impl Menu { // | a | b | c | d | // | .. | me | child_1 | child_of_child | nothing here | // x_max is the x when d is 0 - let b_plus_c = (render_depth + 1) * drop_down_width; + let b_plus_c = dropdown_count_to_go * drop_down_width; let x_max = buf.area().right().saturating_sub(b_plus_c); + let x = x.min(x_max); let area = Rect::new(x, y, drop_down_width, group.len() as u16); @@ -616,7 +621,7 @@ impl Menu { // draw at the end to ensure its content above all items in current level if let Some((x, y, item)) = active_group { - self.render_drop_down(x, y, &item.children, buf); + self.render_dropdown(x, y, &item.children, buf, dropdown_count_to_go - 1); } } } @@ -627,7 +632,7 @@ impl Default for Menu { } } -impl StatefulWidget for Menu { +impl StatefulWidget for Menu { type State = MenuState; fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer, state: &mut Self::State) { @@ -637,6 +642,8 @@ impl StatefulWidget for Menu { let mut x_pos = area.x; let y_pos = area.y; + let dropdown_count = state.dropdown_count(); + for (idx, item) in state.root_item.children.iter().enumerate() { let is_highlight = item.is_highlight; let item_style = if is_highlight { @@ -652,7 +659,7 @@ impl StatefulWidget for Menu { spans.push(span); if has_children && is_highlight { - self.render_drop_down(group_x_pos, y_pos + 1, &item.children, buf); + self.render_dropdown(group_x_pos, y_pos + 1, &item.children, buf, dropdown_count); } if idx < state.root_item.children.len() - 1 { @@ -664,3 +671,136 @@ impl StatefulWidget for Menu { buf.set_line(area.x, area.y, &Line::from(spans), x_pos); } } + +#[cfg(test)] +mod tests { + use crate::MenuState; + + type MenuItem = super::MenuItem; + + #[test] + fn test_active_depth() { + { + let mut menu_state = MenuState::new(vec![MenuItem::item("item1", 0)]); + assert_eq!(menu_state.active_depth(), 0); + } + + { + let mut menu_state = + MenuState::new(vec![MenuItem::item("item1", 0).with_highlight(true)]); + assert_eq!(menu_state.active_depth(), 1); + } + + { + let mut menu_state = MenuState::new(vec![MenuItem::group("layer1", vec![])]); + assert_eq!(menu_state.active_depth(), 0); + } + + { + let mut menu_state = + MenuState::new(vec![MenuItem::group("layer1", vec![]).with_highlight(true)]); + assert_eq!(menu_state.active_depth(), 1); + } + + { + let mut menu_state = MenuState::new(vec![MenuItem::group( + "layer_1", + vec![MenuItem::item("item_layer_2", 0)], + ) + .with_highlight(true)]); + assert_eq!(menu_state.active_depth(), 1); + } + + { + let mut menu_state = MenuState::new(vec![MenuItem::group( + "layer_1", + vec![MenuItem::item("item_layer_2", 0).with_highlight(true)], + ) + .with_highlight(true)]); + assert_eq!(menu_state.active_depth(), 2); + } + } + + #[test] + fn test_dropdown_count() { + { + // only item in menu bar + let mut menu_state = MenuState::new(vec![MenuItem::item("item1", 0)]); + assert_eq!(menu_state.dropdown_count(), 0); + } + + { + // group in menu bar, + let mut menu_state = MenuState::new(vec![MenuItem::group( + "menu bar", + vec![MenuItem::item("item layer 1", 0)], + ) + .with_highlight(true)]); + assert_eq!(menu_state.dropdown_count(), 1); + } + + { + // group in menu bar, + let mut menu_state = MenuState::new(vec![MenuItem::group( + "menu bar 1", + vec![ + MenuItem::group("dropdown 1", vec![MenuItem::item("item layer 2", 0)]) + .with_highlight(true), + MenuItem::item("item layer 1", 0), + ], + ) + .with_highlight(true)]); + assert_eq!(menu_state.dropdown_count(), 2); + } + + { + // *menu bar 1 + // *dropdown 1 > item layer 2 + // item layer 1 group layer 2 > + let mut menu_state = MenuState::new(vec![MenuItem::group( + "menu bar 1", + vec![ + MenuItem::group( + "dropdown 1", + vec![ + MenuItem::item("item layer 2", 0), + MenuItem::group( + "group layer 2", + vec![MenuItem::item("item layer 3", 0)], + ), + ], + ) + .with_highlight(true), + MenuItem::item("item layer 1", 0), + ], + ) + .with_highlight(true)]); + assert_eq!(menu_state.dropdown_count(), 2); + } + + { + // *menu bar 1 + // *dropdown 1 > *item layer 2 + // item layer 1 group layer 2 > item layer 3 + let mut menu_state = MenuState::new(vec![MenuItem::group( + "menu bar 1", + vec![ + MenuItem::group( + "dropdown 1", + vec![ + MenuItem::item("item layer 2", 0).with_highlight(true), + MenuItem::group( + "group layer 2", + vec![MenuItem::item("item layer 3", 0)], + ), + ], + ) + .with_highlight(true), + MenuItem::item("item layer 1", 0), + ], + ) + .with_highlight(true)]); + assert_eq!(menu_state.dropdown_count(), 3); + } + } +} From 2f72b21916b1fe86c9803c706c62b35223df67aa Mon Sep 17 00:00:00 2001 From: shuo Date: Tue, 6 Aug 2024 09:25:30 +0800 Subject: [PATCH 7/7] fix lint --- src/lib.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ffca803..13adc17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -681,29 +681,28 @@ mod tests { #[test] fn test_active_depth() { { - let mut menu_state = MenuState::new(vec![MenuItem::item("item1", 0)]); + let menu_state = MenuState::new(vec![MenuItem::item("item1", 0)]); assert_eq!(menu_state.active_depth(), 0); } { - let mut menu_state = - MenuState::new(vec![MenuItem::item("item1", 0).with_highlight(true)]); + let menu_state = MenuState::new(vec![MenuItem::item("item1", 0).with_highlight(true)]); assert_eq!(menu_state.active_depth(), 1); } { - let mut menu_state = MenuState::new(vec![MenuItem::group("layer1", vec![])]); + let menu_state = MenuState::new(vec![MenuItem::group("layer1", vec![])]); assert_eq!(menu_state.active_depth(), 0); } { - let mut menu_state = + let menu_state = MenuState::new(vec![MenuItem::group("layer1", vec![]).with_highlight(true)]); assert_eq!(menu_state.active_depth(), 1); } { - let mut menu_state = MenuState::new(vec![MenuItem::group( + let menu_state = MenuState::new(vec![MenuItem::group( "layer_1", vec![MenuItem::item("item_layer_2", 0)], ) @@ -712,7 +711,7 @@ mod tests { } { - let mut menu_state = MenuState::new(vec![MenuItem::group( + let menu_state = MenuState::new(vec![MenuItem::group( "layer_1", vec![MenuItem::item("item_layer_2", 0).with_highlight(true)], ) @@ -725,13 +724,13 @@ mod tests { fn test_dropdown_count() { { // only item in menu bar - let mut menu_state = MenuState::new(vec![MenuItem::item("item1", 0)]); + let menu_state = MenuState::new(vec![MenuItem::item("item1", 0)]); assert_eq!(menu_state.dropdown_count(), 0); } { // group in menu bar, - let mut menu_state = MenuState::new(vec![MenuItem::group( + let menu_state = MenuState::new(vec![MenuItem::group( "menu bar", vec![MenuItem::item("item layer 1", 0)], ) @@ -741,7 +740,7 @@ mod tests { { // group in menu bar, - let mut menu_state = MenuState::new(vec![MenuItem::group( + let menu_state = MenuState::new(vec![MenuItem::group( "menu bar 1", vec![ MenuItem::group("dropdown 1", vec![MenuItem::item("item layer 2", 0)]) @@ -757,7 +756,7 @@ mod tests { // *menu bar 1 // *dropdown 1 > item layer 2 // item layer 1 group layer 2 > - let mut menu_state = MenuState::new(vec![MenuItem::group( + let menu_state = MenuState::new(vec![MenuItem::group( "menu bar 1", vec![ MenuItem::group( @@ -782,7 +781,7 @@ mod tests { // *menu bar 1 // *dropdown 1 > *item layer 2 // item layer 1 group layer 2 > item layer 3 - let mut menu_state = MenuState::new(vec![MenuItem::group( + let menu_state = MenuState::new(vec![MenuItem::group( "menu bar 1", vec![ MenuItem::group(