| 111 | } |
| 112 | |
| 113 | pub fn render(&mut self, frame: &mut Frame, area: Rect, theme: &Theme) { |
| 114 | if !self.visible || area.height < 3 { |
| 115 | self.last_area = None; |
| 116 | return; |
| 117 | } |
| 118 | |
| 119 | let items: Vec<ListItem> = self |
| 120 | .items |
| 121 | .iter() |
| 122 | .map(|spec| { |
| 123 | let name_style = theme.style(StyleKind::Primary).add_modifier(Modifier::BOLD); |
| 124 | let desc_style = theme.style(StyleKind::Muted); |
| 125 | let line = Line::from(vec![ |
| 126 | Span::styled(spec.name, name_style), |
| 127 | Span::raw(" - "), |
| 128 | Span::styled(spec.description, desc_style), |
| 129 | ]); |
| 130 | ListItem::new(line) |
| 131 | }) |
| 132 | .collect(); |
| 133 | |
| 134 | let desired_height = (items.len() as u16).saturating_add(2); |
| 135 | let height = desired_height.min(area.height); |
| 136 | if height < 3 { |
| 137 | self.last_area = None; |
| 138 | return; |
| 139 | } |
| 140 | |
| 141 | let menu_area = Rect { |
| 142 | x: area.x, |
| 143 | y: area.y + area.height.saturating_sub(height), |
| 144 | width: area.width, |
| 145 | height, |
| 146 | }; |
| 147 | self.last_area = Some(menu_area); |
| 148 | |
| 149 | let block = Block::default() |
| 150 | .borders(Borders::ALL) |
| 151 | .border_style(theme.style(StyleKind::Border)) |
| 152 | .style(Style::default().bg(theme.background)) |
| 153 | .title(" Commands "); |
| 154 | |
| 155 | let list = List::new(items) |
| 156 | .block(block) |
| 157 | .style(Style::default().bg(theme.background)) |
| 158 | .highlight_style( |
| 159 | Style::default() |
| 160 | .bg(theme.primary) |
| 161 | .fg(theme.selection_foreground()) |
| 162 | .add_modifier(Modifier::BOLD), |
| 163 | ); |
| 164 | |
| 165 | frame.render_widget(Clear, menu_area); |
| 166 | frame.render_stateful_widget(list, menu_area, &mut self.list_state); |
| 167 | } |
| 168 | |
| 169 | /// Handle mouse events. Returns `Some(command_name)` when a command is clicked. |
| 170 | pub fn handle_mouse_event(&mut self, mouse: &MouseEvent) -> Option<String> { |