Skip to main content

datui_lib/widgets/
chart_export_modal.rs

1//! Chart export modal rendering: format (PNG/EPS), optional title, and path.
2
3use crate::chart_export::ChartExportFormat;
4use crate::chart_export_modal::{ChartExportFocus, ChartExportModal};
5use ratatui::layout::{Constraint, Direction, Layout, Rect};
6use ratatui::style::Style;
7use ratatui::text::{Line, Span};
8use ratatui::widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget};
9
10const FORMAT_COLS: u16 = 3;
11
12pub fn render_chart_export_modal(
13    area: Rect,
14    buf: &mut ratatui::buffer::Buffer,
15    modal: &mut ChartExportModal,
16    border_color: ratatui::style::Color,
17    active_color: ratatui::style::Color,
18) {
19    Clear.render(area, buf);
20    let block = Block::default()
21        .borders(Borders::ALL)
22        .border_type(BorderType::Rounded)
23        .border_style(Style::default().fg(border_color))
24        .title(" Export Chart ");
25    let inner = block.inner(area);
26    block.render(area, buf);
27
28    let chunks = Layout::default()
29        .direction(Direction::Vertical)
30        .constraints([
31            Constraint::Length(3), // Format row
32            Constraint::Length(3), // Chart title (optional)
33            Constraint::Length(3), // Path row
34            Constraint::Length(3), // Buttons
35        ])
36        .split(inner);
37
38    // Format: 3-column grid, only as many rows as needed (2 options = 1 row)
39    let format_area = chunks[0];
40    let is_format_focused = modal.focus == ChartExportFocus::FormatSelector;
41    let format_block = Block::default()
42        .borders(Borders::ALL)
43        .border_type(BorderType::Rounded)
44        .border_style(Style::default().fg(if is_format_focused {
45            active_color
46        } else {
47            border_color
48        }))
49        .title(" Format ");
50    let format_inner = format_block.inner(format_area);
51    format_block.render(format_area, buf);
52
53    let col_width = format_inner.width / FORMAT_COLS;
54    for (i, &format) in ChartExportFormat::ALL.iter().enumerate() {
55        let row = i / FORMAT_COLS as usize;
56        let col = i % FORMAT_COLS as usize;
57        let cell_x = format_inner.x + (col as u16 * col_width);
58        let cell_y = format_inner.y + row as u16;
59        if cell_y >= format_inner.bottom() {
60            break;
61        }
62        let cell_area = Rect {
63            x: cell_x,
64            y: cell_y,
65            width: col_width,
66            height: 1,
67        };
68        let marker = if modal.selected_format == format {
69            "●"
70        } else {
71            "○"
72        };
73        let style = if modal.selected_format == format {
74            Style::default().fg(active_color)
75        } else {
76            Style::default().fg(border_color)
77        };
78        Paragraph::new(Line::from(Span::styled(
79            format!("{} {}", marker, format.as_str()),
80            style,
81        )))
82        .render(cell_area, buf);
83    }
84
85    // Chart title (optional; blank = no title on export)
86    let title_area = chunks[1];
87    let is_title_focused = modal.focus == ChartExportFocus::TitleInput;
88    let title_block = Block::default()
89        .borders(Borders::ALL)
90        .border_type(BorderType::Rounded)
91        .border_style(Style::default().fg(if is_title_focused {
92            active_color
93        } else {
94            border_color
95        }))
96        .title(" Chart Title ");
97    let title_inner = title_block.inner(title_area);
98    title_block.render(title_area, buf);
99    modal.title_input.set_focused(is_title_focused);
100    (&modal.title_input).render(title_inner, buf);
101
102    // Path input
103    let path_area = chunks[2];
104    let is_path_focused = modal.focus == ChartExportFocus::PathInput;
105    let path_block = Block::default()
106        .borders(Borders::ALL)
107        .border_type(BorderType::Rounded)
108        .border_style(Style::default().fg(if is_path_focused {
109            active_color
110        } else {
111            border_color
112        }))
113        .title(" File Path ");
114    let path_inner = path_block.inner(path_area);
115    path_block.render(path_area, buf);
116    modal.path_input.set_focused(is_path_focused);
117    (&modal.path_input).render(path_inner, buf);
118
119    // Buttons
120    let btn_area = chunks[3];
121    let btn_chunks = Layout::default()
122        .direction(Direction::Horizontal)
123        .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
124        .split(btn_area);
125
126    let is_export_focused = modal.focus == ChartExportFocus::ExportButton;
127    Paragraph::new("Export")
128        .style(if is_export_focused {
129            Style::default().fg(active_color)
130        } else {
131            Style::default().fg(border_color)
132        })
133        .block(
134            Block::default()
135                .borders(Borders::ALL)
136                .border_type(BorderType::Rounded)
137                .border_style(if is_export_focused {
138                    Style::default().fg(active_color)
139                } else {
140                    Style::default().fg(border_color)
141                }),
142        )
143        .centered()
144        .render(btn_chunks[0], buf);
145
146    let is_cancel_focused = modal.focus == ChartExportFocus::CancelButton;
147    Paragraph::new("Cancel")
148        .style(if is_cancel_focused {
149            Style::default().fg(active_color)
150        } else {
151            Style::default().fg(border_color)
152        })
153        .block(
154            Block::default()
155                .borders(Borders::ALL)
156                .border_type(BorderType::Rounded)
157                .border_style(if is_cancel_focused {
158                    Style::default().fg(active_color)
159                } else {
160                    Style::default().fg(border_color)
161                }),
162        )
163        .centered()
164        .render(btn_chunks[1], buf);
165}