1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::Style,
5 text::{Line, Span},
6 widgets::{Block, BorderType, Borders, Paragraph, Widget, Wrap},
7};
8
9use crate::explain::ExplainNode;
10use crate::ui::theme;
11
12pub struct ExplanationPanel<'a> {
13 pub nodes: &'a [ExplainNode],
14 pub error: Option<&'a str>,
15 pub scroll: u16,
16 pub focused: bool,
17 pub border_type: BorderType,
18}
19
20impl<'a> Widget for ExplanationPanel<'a> {
21 fn render(self, area: Rect, buf: &mut Buffer) {
22 let border_color = if self.focused {
23 theme::BLUE
24 } else {
25 theme::OVERLAY
26 };
27 let block = Block::default()
28 .borders(Borders::ALL)
29 .border_type(self.border_type)
30 .border_style(Style::default().fg(border_color))
31 .title(Span::styled(
32 " Explanation ",
33 Style::default().fg(theme::TEXT),
34 ));
35
36 if let Some(err) = self.error {
37 let lines = vec![Line::from(Span::styled(
38 err.to_string(),
39 Style::default().fg(theme::RED),
40 ))];
41 let paragraph = Paragraph::new(lines)
42 .block(block)
43 .style(Style::default().bg(theme::BASE));
44 paragraph.render(area, buf);
45 return;
46 }
47
48 if self.nodes.is_empty() {
49 let paragraph = Paragraph::new(Line::from(Span::styled(
50 "Enter a pattern to see its explanation",
51 Style::default().fg(theme::SUBTEXT),
52 )))
53 .block(block)
54 .style(Style::default().bg(theme::BASE));
55 paragraph.render(area, buf);
56 return;
57 }
58
59 let lines: Vec<Line> = self
60 .nodes
61 .iter()
62 .map(|node| {
63 let indent = " ".repeat(node.depth);
64 let bullet = if node.depth > 0 { "|- " } else { "" };
65 Line::from(Span::styled(
66 format!("{indent}{bullet}{}", node.description),
67 Style::default().fg(if node.depth == 0 {
68 theme::TEXT
69 } else {
70 theme::SUBTEXT
71 }),
72 ))
73 })
74 .collect();
75
76 let paragraph = Paragraph::new(lines)
77 .block(block)
78 .style(Style::default().bg(theme::BASE))
79 .wrap(Wrap { trim: false })
80 .scroll((self.scroll, 0));
81
82 paragraph.render(area, buf);
83 }
84}