/ apps / microtop / src / ui / overlays.rs
overlays.rs
  1  use crate::app::{App, COMMAND_PALETTE_ITEMS, CommandPaletteGroup};
  2  use ratatui::{
  3      Frame,
  4      layout::{Constraint, Layout},
  5      style::{Color, Modifier, Style, Stylize},
  6      text::{Line, Span},
  7      widgets::{Block, BorderType, Borders, Clear, List, ListItem, Paragraph, Widget},
  8  };
  9  
 10  pub fn render_command_palette(frame: &mut Frame, app: &App) {
 11      let popup_area = Layout::vertical([
 12          Constraint::Percentage(22),
 13          Constraint::Length(16),
 14          Constraint::Fill(1),
 15      ])
 16      .split(frame.area())[1];
 17      let popup_area = Layout::horizontal([
 18          Constraint::Percentage(24),
 19          Constraint::Length(66),
 20          Constraint::Fill(1),
 21      ])
 22      .split(popup_area)[1];
 23  
 24      Clear.render(popup_area, frame.buffer_mut());
 25  
 26      let popup_block = Block::bordered()
 27          .border_type(BorderType::Rounded)
 28          .border_style(Color::DarkGray)
 29          .title(" Command Palette ".yellow());
 30      let popup_inner_area = popup_block.inner(popup_area);
 31      popup_block.render(popup_area, frame.buffer_mut());
 32  
 33      let popup_layout =
 34          Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).split(popup_inner_area);
 35  
 36      let input_line = Line::from(vec![
 37          Span::styled(" 🔍 ", Style::default().fg(Color::DarkGray)),
 38          Span::styled(
 39              if app.command_palette_query.is_empty() {
 40                  "Type a command or search..."
 41              } else {
 42                  app.command_palette_query.as_str()
 43              },
 44              if app.command_palette_query.is_empty() {
 45                  Style::default().fg(Color::DarkGray)
 46              } else {
 47                  Style::default().fg(Color::Yellow)
 48              },
 49          ),
 50      ]);
 51      Paragraph::new(input_line).render(popup_layout[0], frame.buffer_mut());
 52  
 53      let filtered_item_indices = app.filtered_command_item_indices();
 54      let selected_index = app.command_palette_selected_index();
 55      let mut visible_item_index = 0usize;
 56      let mut active_group: Option<CommandPaletteGroup> = None;
 57      let mut command_items = Vec::new();
 58  
 59      for item_index in filtered_item_indices {
 60          let command_item = &COMMAND_PALETTE_ITEMS[item_index];
 61  
 62          if active_group != Some(command_item.group) {
 63              active_group = Some(command_item.group);
 64              let heading = match command_item.group {
 65                  CommandPaletteGroup::Actions => "Actions",
 66                  CommandPaletteGroup::Navigate => "Navigate",
 67              };
 68              command_items.push(ListItem::new(Line::from(vec![Span::styled(
 69                  format!(" {heading}"),
 70                  Style::default()
 71                      .fg(Color::DarkGray)
 72                      .add_modifier(Modifier::BOLD),
 73              )])));
 74          }
 75  
 76          let command_item_style = if visible_item_index == selected_index {
 77              Style::default()
 78                  .fg(Color::Yellow)
 79                  .bg(Color::Rgb(50, 34, 12))
 80                  .add_modifier(Modifier::BOLD)
 81          } else {
 82              Style::default().fg(Color::Gray)
 83          };
 84  
 85          command_items.push(ListItem::new(Line::from(vec![
 86              Span::styled(format!(" {} ", command_item.icon), command_item_style),
 87              Span::styled(command_item.label, command_item_style),
 88              Span::styled(" ", command_item_style),
 89              Span::styled(command_item.shortcut, Style::default().fg(Color::DarkGray)),
 90          ])));
 91  
 92          visible_item_index += 1;
 93      }
 94  
 95      if command_items.is_empty() {
 96          command_items.push(ListItem::new(Line::from(vec![Span::styled(
 97              " No results found.",
 98              Style::default().fg(Color::DarkGray),
 99          )])));
100      }
101  
102      List::new(command_items)
103          .block(Block::default().borders(Borders::TOP))
104          .render(popup_layout[1], frame.buffer_mut());
105  }
106  
107  pub fn render_api_base_url_editor(frame: &mut Frame, app: &App) {
108      let popup_area = Layout::vertical([
109          Constraint::Percentage(28),
110          Constraint::Length(7),
111          Constraint::Fill(1),
112      ])
113      .split(frame.area())[1];
114      let popup_area = Layout::horizontal([
115          Constraint::Percentage(22),
116          Constraint::Length(72),
117          Constraint::Fill(1),
118      ])
119      .split(popup_area)[1];
120  
121      Clear.render(popup_area, frame.buffer_mut());
122  
123      let popup_block = Block::bordered()
124          .border_type(BorderType::Rounded)
125          .border_style(Color::Yellow)
126          .title(" Device API URL ".yellow().bold());
127      let popup_inner_area = popup_block.inner(popup_area);
128      popup_block.render(popup_area, frame.buffer_mut());
129  
130      let lines = vec![
131          Line::from(vec![
132              Span::styled(" URL: ", Style::default().fg(Color::DarkGray)),
133              Span::styled(
134                  app.api_base_url_editor_buffer.as_str(),
135                  Style::default()
136                      .fg(Color::Yellow)
137                      .add_modifier(Modifier::BOLD),
138              ),
139          ]),
140          Line::from(vec![
141              Span::styled(" Enter ", Style::default().fg(Color::DarkGray)),
142              Span::styled("apply", Style::default().fg(Color::Yellow)),
143              Span::styled("  Esc ", Style::default().fg(Color::DarkGray)),
144              Span::styled("cancel", Style::default().fg(Color::Yellow)),
145          ]),
146      ];
147  
148      Paragraph::new(lines).render(popup_inner_area, frame.buffer_mut());
149  }