use crate::support::text::{self, format_time_local};
use crate::tui::core::ScrollIden;
use crate::tui::support::UiExt as _;
use crate::tui::view::comp;
use crate::tui::view::support::RectExt as _;
use crate::tui::{AppState, style};
use ratatui::buffer::Buffer;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::Stylize;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, HighlightSpacing, List, ListItem, ListState, Paragraph, StatefulWidget, Widget as _};
pub struct RunsNavView;
impl RunsNavView {
const NAV_SCROLL_IDEN: ScrollIden = ScrollIden::RunsNav;
pub fn clear_scroll_idens(state: &mut AppState) {
state.clear_scroll_zone_area(&Self::NAV_SCROLL_IDEN);
}
}
impl StatefulWidget for RunsNavView {
type State = AppState;
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
const SCROLL_IDEN: ScrollIden = RunsNavView::NAV_SCROLL_IDEN;
Block::new().bg(style::CLR_BKG_GRAY_DARKER).render(area, buf);
Block::new().render(area, buf);
let [label_a, list_a] = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Length(1), Constraint::Fill(1)])
.areas(area);
Paragraph::new(" Runs: ")
.style(style::STL_FIELD_LBL)
.left_aligned()
.render(label_a, buf);
state.set_scroll_area(SCROLL_IDEN, list_a);
let runs_len = state.run_items().len();
let scroll = state.clamp_scroll(SCROLL_IDEN, runs_len);
if process_mouse_for_run_nav(state, list_a, scroll) {
state.trigger_redraw();
return;
}
let runs = state.run_items();
let run_sel_idx = state.run_idx().unwrap_or_default();
let is_mouse_in_nav = state.is_last_mouse_over(list_a);
let items: Vec<ListItem> = runs
.iter()
.enumerate()
.map(|(idx, run_item)| {
let run = run_item.run();
let run_ico = comp::el_running_ico(run);
let label = if let Some(_parent_id) = run_item.parent_id() {
run.agent_name.as_deref().unwrap_or("no agent name").to_string()
} else if let Some(start) = run.start
&& let Ok(start_fmt) = format_time_local(start.into())
{
start_fmt
} else {
format!("Run {idx}")
};
let prefix = text::spaces_up_to_10(run_item.indent() + 1);
let label = run.label.clone().unwrap_or(label);
let mut line = Line::from(vec![
Span::raw(prefix),
run_ico,
Span::raw(" "),
Span::styled(label, style::STL_TXT),
]);
if run_sel_idx == idx {
line = line.style(style::STL_NAV_ITEM_HIGHLIGHT);
line = line.x_fg(style::CLR_TXT_BLACK);
} else if is_mouse_in_nav && state.is_last_mouse_over(list_a.x_row((idx + 1) as u16 - scroll)) {
line = line.fg(style::CLR_TXT_HOVER);
};
ListItem::new(line)
})
.collect();
let item_count = items.len();
let list_w = List::new(items)
.highlight_spacing(HighlightSpacing::WhenSelected);
let mut list_s = ListState::default().with_offset(scroll as usize);
StatefulWidget::render(list_w, list_a, buf, &mut list_s);
let item_count = item_count as u16;
if item_count - scroll > list_a.height {
let bottom_ico = list_a.x_bottom_right(1, 1);
comp::ico_scroll_down().render(bottom_ico, buf);
}
if scroll > 0 && item_count > list_a.height - scroll {
let top_ico = list_a.x_top_right(1, 1);
comp::ico_scroll_up().render(top_ico, buf);
}
}
}
fn process_mouse_for_run_nav(state: &mut AppState, nav_a: Rect, scroll: u16) -> bool {
if let Some(mouse_evt) = state.mouse_evt()
&& mouse_evt.is_up()
&& mouse_evt.is_over(nav_a)
{
let current_run_idx = state.run_idx();
let new_idx = mouse_evt.y() - nav_a.y + scroll;
let runs_len = state.run_items().len();
let new_idx = new_idx as usize;
if new_idx >= runs_len {
return false;
}
if Some(new_idx) != current_run_idx {
state.set_run_idx(Some(new_idx));
return true;
}
}
false
}