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}