ratatui_toolkit/primitives/dialog/widget/traits/
widget.rs1use 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}