ratatui_toolkit/primitives/dialog/widget/traits/
widget.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::{Alignment, Constraint, Direction, Layout, Rect},
4    style::{Modifier, Style},
5    text::{Line, Span},
6    widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget, Wrap},
7};
8
9use crate::primitives::dialog::DialogWidget;
10
11impl Widget for DialogWidget<'_> {
12    fn render(self, area: Rect, buf: &mut Buffer) {
13        if self.dialog.overlay {
14            let overlay = Paragraph::new("").style(self.dialog.overlay_style);
15            overlay.render(area, buf);
16        }
17
18        let dialog_width = (area.width as f32 * self.dialog.width_percent) as u16;
19        let dialog_height = (area.height as f32 * self.dialog.height_percent) as u16;
20        let dialog_x = (area.width.saturating_sub(dialog_width)) / 2;
21        let dialog_y = (area.height.saturating_sub(dialog_height)) / 2;
22
23        let dialog_area = Rect {
24            x: area.x + dialog_x,
25            y: area.y + dialog_y,
26            width: dialog_width,
27            height: dialog_height,
28        };
29
30        Clear.render(dialog_area, buf);
31
32        let block = if self.dialog.title_inside {
33            Block::default()
34                .borders(Borders::ALL)
35                .border_type(BorderType::Rounded)
36                .border_style(Style::default().fg(self.dialog.get_border_color()))
37                .style(self.dialog.style)
38        } else {
39            Block::default()
40                .title(self.dialog.title)
41                .borders(Borders::ALL)
42                .border_type(BorderType::Rounded)
43                .border_style(Style::default().fg(self.dialog.get_border_color()))
44                .style(self.dialog.style)
45        };
46
47        let inner = block.inner(dialog_area);
48        block.render(dialog_area, buf);
49
50        let chunks = Layout::default()
51            .direction(Direction::Vertical)
52            .constraints([Constraint::Min(3), Constraint::Length(3)])
53            .split(inner);
54
55        let mut lines: Vec<Line> = Vec::new();
56
57        if self.dialog.title_inside {
58            lines.push(Line::from(vec![Span::styled(
59                self.dialog.title,
60                Style::default()
61                    .add_modifier(Modifier::BOLD)
62                    .fg(self.dialog.get_border_color()),
63            )]));
64            lines.push(Line::from(""));
65        }
66
67        for line in self.dialog.message.lines() {
68            lines.push(Line::from(line));
69        }
70
71        if let Some(footer) = self.dialog.footer {
72            if !lines.is_empty() {
73                lines.push(Line::from(""));
74            }
75            lines.push(Line::from(vec![Span::styled(
76                footer,
77                self.dialog.footer_style,
78            )]));
79        }
80
81        let message = Paragraph::new(lines)
82            .style(self.dialog.style)
83            .alignment(Alignment::Center)
84            .wrap(Wrap { trim: true });
85        message.render(chunks[0], buf);
86
87        self.dialog.button_areas.clear();
88
89        if !self.dialog.buttons.is_empty() {
90            let total_button_width: usize = self.dialog.buttons.iter().map(|b| b.len() + 4).sum();
91            let button_area_width = chunks[1].width as usize;
92            let start_x = if total_button_width < button_area_width {
93                chunks[1].x + ((button_area_width - total_button_width) / 2) as u16
94            } else {
95                chunks[1].x
96            };
97
98            let mut x = start_x;
99            let y = chunks[1].y + 1;
100
101            for (idx, button_text) in self.dialog.buttons.iter().enumerate() {
102                let button_width = button_text.len() as u16 + 2;
103                let style = if idx == self.dialog.selected_button {
104                    self.dialog.button_selected_style
105                } else {
106                    self.dialog.button_style
107                };
108
109                let button_area = Rect {
110                    x,
111                    y,
112                    width: button_width,
113                    height: 1,
114                };
115
116                self.dialog.button_areas.push(button_area);
117
118                for bx in x..x + button_width {
119                    if let Some(cell) = buf.cell_mut((bx, y)) {
120                        cell.set_style(style);
121                    }
122                }
123
124                let button_line =
125                    Line::from(vec![Span::styled(format!(" {} ", button_text), style)]);
126
127                buf.set_line(x, y, &button_line, button_width);
128                x += button_width + 2;
129            }
130        }
131    }
132}