1use ratatui::{
2 buffer::Buffer,
3 layout::{Alignment, Constraint, Layout, Rect},
4 style::{Color, Modifier, Style},
5 text::{Line, Span},
6 widgets::{Block, Borders, Clear, Paragraph, Widget},
7};
8
9pub struct QuitDialog;
11
12impl QuitDialog {
13 pub fn new() -> Self {
14 Self
15 }
16
17 fn dialog_area(area: Rect) -> Rect {
19 let dialog_width = 50.min(area.width.saturating_sub(4));
20 let dialog_height = 7.min(area.height.saturating_sub(2));
21
22 let x = (area.width.saturating_sub(dialog_width)) / 2;
23 let y = (area.height.saturating_sub(dialog_height)) / 2;
24
25 Rect::new(x, y, dialog_width, dialog_height)
26 }
27}
28
29impl Widget for QuitDialog {
30 fn render(self, area: Rect, buf: &mut Buffer) {
31 let dialog_area = Self::dialog_area(area);
32
33 Clear.render(dialog_area, buf);
35
36 let block = Block::default()
38 .title(" Unsaved Changes ")
39 .title_alignment(Alignment::Center)
40 .borders(Borders::ALL)
41 .border_style(Style::default().fg(Color::Yellow))
42 .style(Style::default().bg(Color::Black));
43
44 let inner_area = block.inner(dialog_area);
45 block.render(dialog_area, buf);
46
47 let chunks = Layout::vertical([
49 Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), ])
54 .split(inner_area);
55
56 let message = Paragraph::new("You have unsaved changes. Quit anyway?")
58 .alignment(Alignment::Center)
59 .style(Style::default().fg(Color::White));
60 message.render(chunks[1], buf);
61
62 let buttons = Line::from(vec![
64 Span::styled(
65 " [Y] ",
66 Style::default()
67 .fg(Color::Black)
68 .bg(Color::Red)
69 .add_modifier(Modifier::BOLD),
70 ),
71 Span::raw(" Quit "),
72 Span::styled(
73 " [N] ",
74 Style::default()
75 .fg(Color::Black)
76 .bg(Color::Green)
77 .add_modifier(Modifier::BOLD),
78 ),
79 Span::raw(" Cancel "),
80 ]);
81 let buttons_para = Paragraph::new(buttons).alignment(Alignment::Center);
82 buttons_para.render(chunks[3], buf);
83 }
84}
85
86impl Default for QuitDialog {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92pub struct SaveAsDialog<'a> {
94 filename: &'a str,
95}
96
97impl<'a> SaveAsDialog<'a> {
98 pub fn new(filename: &'a str) -> Self {
99 Self { filename }
100 }
101
102 fn dialog_area(area: Rect) -> Rect {
104 let dialog_width = 60.min(area.width.saturating_sub(4));
105 let dialog_height = 7.min(area.height.saturating_sub(2));
106
107 let x = (area.width.saturating_sub(dialog_width)) / 2;
108 let y = (area.height.saturating_sub(dialog_height)) / 2;
109
110 Rect::new(x, y, dialog_width, dialog_height)
111 }
112}
113
114impl<'a> Widget for SaveAsDialog<'a> {
115 fn render(self, area: Rect, buf: &mut Buffer) {
116 let dialog_area = Self::dialog_area(area);
117
118 Clear.render(dialog_area, buf);
120
121 let block = Block::default()
123 .title(" Save As ")
124 .title_alignment(Alignment::Center)
125 .borders(Borders::ALL)
126 .border_style(Style::default().fg(Color::Cyan))
127 .style(Style::default().bg(Color::Black));
128
129 let inner_area = block.inner(dialog_area);
130 block.render(dialog_area, buf);
131
132 let chunks = Layout::vertical([
134 Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), ])
139 .split(inner_area);
140
141 let prompt = Paragraph::new("Enter filename:").style(Style::default().fg(Color::White));
143 prompt.render(chunks[1], buf);
144
145 let input = Paragraph::new(self.filename).style(
147 Style::default()
148 .fg(Color::Yellow)
149 .add_modifier(Modifier::BOLD),
150 );
151 input.render(chunks[2], buf);
152
153 let hint = Paragraph::new("Press Enter to save, Esc to cancel")
155 .alignment(Alignment::Center)
156 .style(Style::default().fg(Color::DarkGray));
157 hint.render(chunks[3], buf);
158 }
159}
160
161pub struct GotoLineDialog<'a> {
163 line_number: &'a str,
164 current_line: usize,
165 total_lines: usize,
166}
167
168impl<'a> GotoLineDialog<'a> {
169 pub fn new(line_number: &'a str, current_line: usize, total_lines: usize) -> Self {
170 Self {
171 line_number,
172 current_line,
173 total_lines,
174 }
175 }
176
177 fn dialog_area(area: Rect) -> Rect {
179 let dialog_width = 50.min(area.width.saturating_sub(4));
180 let dialog_height = 8.min(area.height.saturating_sub(2));
181
182 let x = (area.width.saturating_sub(dialog_width)) / 2;
183 let y = (area.height.saturating_sub(dialog_height)) / 2;
184
185 Rect::new(x, y, dialog_width, dialog_height)
186 }
187}
188
189impl<'a> Widget for GotoLineDialog<'a> {
190 fn render(self, area: Rect, buf: &mut Buffer) {
191 let dialog_area = Self::dialog_area(area);
192
193 Clear.render(dialog_area, buf);
195
196 let block = Block::default()
198 .title(" Go to Line ")
199 .title_alignment(Alignment::Center)
200 .borders(Borders::ALL)
201 .border_style(Style::default().fg(Color::Cyan))
202 .style(Style::default().bg(Color::Black));
203
204 let inner_area = block.inner(dialog_area);
205 block.render(dialog_area, buf);
206
207 let chunks = Layout::vertical([
209 Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), Constraint::Length(1), ])
215 .split(inner_area);
216
217 let info = Paragraph::new(format!(
219 "Current: {} / {}",
220 self.current_line + 1,
221 self.total_lines
222 ))
223 .alignment(Alignment::Center)
224 .style(Style::default().fg(Color::DarkGray));
225 info.render(chunks[1], buf);
226
227 let prompt = Paragraph::new("Enter line number:").style(Style::default().fg(Color::White));
229 prompt.render(chunks[2], buf);
230
231 let input = Paragraph::new(self.line_number).style(
233 Style::default()
234 .fg(Color::Yellow)
235 .add_modifier(Modifier::BOLD),
236 );
237 input.render(chunks[3], buf);
238
239 let hint = Paragraph::new("Press Enter to jump, Esc to cancel")
241 .alignment(Alignment::Center)
242 .style(Style::default().fg(Color::DarkGray));
243 hint.render(chunks[4], buf);
244 }
245}