monade-mprocs 0.3.0

A fork of the popular mprocs utility, includable via cargo as a library
Documentation
use tui::{
  layout::{Margin, Rect},
  style::{Color, Modifier, Style},
  text::{Span, Spans},
  widgets::{List, ListItem, ListState},
  Frame,
};

use crate::{
  proc::Proc,
  protocol::ProxyBackend,
  state::{Scope, State},
  theme::Theme,
};

type Backend = ProxyBackend;

pub fn render_procs(area: Rect, frame: &mut Frame<Backend>, state: &mut State) {
  if area.width <= 2 {
    return;
  }

  let theme = Theme::default();
  let theme = &theme;

  let active = state.scope == Scope::Procs;

  let mut list_state = ListState::default();
  list_state.select(Some(state.selected));
  let items = state
    .procs
    .iter_mut()
    .enumerate()
    .map(|(i, proc)| {
      create_proc_item(proc, i == state.selected, area.width - 2, theme)
    })
    .collect::<Vec<_>>();

  let title = {
    let mut spans = vec![Span::styled("Processes", theme.pane_title(active))];
    if state.quitting {
      spans.push(Span::from(" "));
      spans.push(Span::styled(
        "QUITTING",
        Style::default()
          .fg(Color::Black)
          .bg(Color::Red)
          .add_modifier(Modifier::BOLD),
      ));
    }
    spans
  };

  let items = List::new(items)
    .block(theme.pane(active).title(title))
    .style(Style::default().fg(Color::White));
  frame.render_stateful_widget(items, area, &mut list_state);
}

fn create_proc_item<'a>(
  proc: &mut Proc,
  is_cur: bool,
  width: u16,
  theme: &Theme,
) -> ListItem<'a> {
  let status = if proc.is_up() {
    Span::styled(
      " UP ",
      Style::default()
        .fg(Color::LightGreen)
        .add_modifier(Modifier::BOLD),
    )
  } else {
    Span::styled(" DOWN ", Style::default().fg(Color::LightRed))
  };

  let mark = if is_cur {
    Span::raw("")
  } else {
    Span::raw(" ")
  };

  let mut name = proc.name.clone();
  let name_max = (width as usize)
    .saturating_sub(mark.width())
    .saturating_sub(status.width());
  let name_len = name.chars().count();
  if name_len > name_max {
    name.truncate(
      name
        .char_indices()
        .nth(name_max)
        .map_or(name.len(), |(n, _)| n),
    )
  }
  if name_len < name_max {
    for _ in name_len..name_max {
      name.push(' ');
    }
  }

  let name_style = Style::default();
  let name_style = if proc.changed {
    name_style.add_modifier(Modifier::BOLD)
  } else {
    name_style
  };
  let name = Span::styled(name, name_style);

  ListItem::new(Spans::from(vec![mark, name, status]))
    .style(theme.get_procs_item(is_cur))
}

pub fn procs_get_clicked_index(
  area: Rect,
  x: u16,
  y: u16,
  state: &State,
) -> Option<usize> {
  let inner = area.inner(&Margin {
    vertical: 1,
    horizontal: 1,
  });
  if procs_check_hit(area, x, y) {
    let index = y - inner.y;
    let scroll = (state.selected + 1).saturating_sub(inner.height as usize);
    let index = index as usize + scroll;
    if index < state.procs.len() {
      return Some(index as usize);
    }
  }
  None
}

pub fn procs_check_hit(area: Rect, x: u16, y: u16) -> bool {
  area.x < x
    && area.x + area.width > x + 1
    && area.y < y
    && area.y + area.height > y + 1
}