mprocs/
ui_procs.rs

1use tui::{
2  layout::{Margin, Rect},
3  style::{Color, Modifier, Style},
4  text::{Span, Spans},
5  widgets::{List, ListItem, ListState},
6  Frame,
7};
8
9use crate::{
10  proc::Proc,
11  protocol::ProxyBackend,
12  state::{Scope, State},
13  theme::Theme,
14};
15
16type Backend = ProxyBackend;
17
18pub fn render_procs(area: Rect, frame: &mut Frame<Backend>, state: &mut State) {
19  if area.width <= 2 {
20    return;
21  }
22
23  let theme = Theme::default();
24  let theme = &theme;
25
26  let active = state.scope == Scope::Procs;
27
28  let mut list_state = ListState::default();
29  list_state.select(Some(state.selected));
30  let items = state
31    .procs
32    .iter_mut()
33    .enumerate()
34    .map(|(i, proc)| {
35      create_proc_item(proc, i == state.selected, area.width - 2, theme)
36    })
37    .collect::<Vec<_>>();
38
39  let title = {
40    let mut spans = vec![Span::styled("Processes", theme.pane_title(active))];
41    if state.quitting {
42      spans.push(Span::from(" "));
43      spans.push(Span::styled(
44        "QUITTING",
45        Style::default()
46          .fg(Color::Black)
47          .bg(Color::Red)
48          .add_modifier(Modifier::BOLD),
49      ));
50    }
51    spans
52  };
53
54  let items = List::new(items)
55    .block(theme.pane(active).title(title))
56    .style(Style::default().fg(Color::White));
57  frame.render_stateful_widget(items, area, &mut list_state);
58}
59
60fn create_proc_item<'a>(
61  proc: &mut Proc,
62  is_cur: bool,
63  width: u16,
64  theme: &Theme,
65) -> ListItem<'a> {
66  let status = if proc.is_up() {
67    Span::styled(
68      " UP ",
69      Style::default()
70        .fg(Color::LightGreen)
71        .add_modifier(Modifier::BOLD),
72    )
73  } else {
74    Span::styled(" DOWN ", Style::default().fg(Color::LightRed))
75  };
76
77  let mark = if is_cur {
78    Span::raw("•")
79  } else {
80    Span::raw(" ")
81  };
82
83  let mut name = proc.name.clone();
84  let name_max = (width as usize)
85    .saturating_sub(mark.width())
86    .saturating_sub(status.width());
87  let name_len = name.chars().count();
88  if name_len > name_max {
89    name.truncate(
90      name
91        .char_indices()
92        .nth(name_max)
93        .map_or(name.len(), |(n, _)| n),
94    )
95  }
96  if name_len < name_max {
97    for _ in name_len..name_max {
98      name.push(' ');
99    }
100  }
101
102  let name_style = Style::default();
103  let name_style = if proc.changed {
104    name_style.add_modifier(Modifier::BOLD)
105  } else {
106    name_style
107  };
108  let name = Span::styled(name, name_style);
109
110  ListItem::new(Spans::from(vec![mark, name, status]))
111    .style(theme.get_procs_item(is_cur))
112}
113
114pub fn procs_get_clicked_index(
115  area: Rect,
116  x: u16,
117  y: u16,
118  state: &State,
119) -> Option<usize> {
120  let inner = area.inner(&Margin {
121    vertical: 1,
122    horizontal: 1,
123  });
124  if procs_check_hit(area, x, y) {
125    let index = y - inner.y;
126    let scroll = (state.selected + 1).saturating_sub(inner.height as usize);
127    let index = index as usize + scroll;
128    if index < state.procs.len() {
129      return Some(index as usize);
130    }
131  }
132  None
133}
134
135pub fn procs_check_hit(area: Rect, x: u16, y: u16) -> bool {
136  area.x < x
137    && area.x + area.width > x + 1
138    && area.y < y
139    && area.y + area.height > y + 1
140}