1use crate::_private::NonExhaustive;
44use crate::button::{Button, ButtonState, ButtonStyle};
45use crate::event::ButtonOutcome;
46use crate::layout::{DialogItem, layout_dialog};
47use crate::paragraph::{Paragraph, ParagraphState};
48use crate::util::{block_padding2, reset_buf_area};
49use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
50use rat_event::{ConsumedEvent, Dialog, HandleEvent, Outcome, Regular, ct_event};
51use rat_focus::{Focus, FocusBuilder};
52use rat_scrolled::{Scroll, ScrollStyle};
53use ratatui::buffer::Buffer;
54use ratatui::layout::{Alignment, Constraint, Flex, Rect};
55use ratatui::style::Style;
56use ratatui::text::{Line, Text};
57use ratatui::widgets::{Block, Padding, StatefulWidget, Widget};
58use std::cell::{Cell, RefCell};
59use std::cmp::max;
60use std::fmt::Debug;
61
62#[derive(Debug, Default, Clone)]
64pub struct MsgDialog<'a> {
65 style: Style,
66 scroll_style: Option<ScrollStyle>,
67 button_style: Option<ButtonStyle>,
68 block: Option<Block<'a>>,
69}
70
71#[derive(Debug, Clone)]
73pub struct MsgDialogStyle {
74 pub style: Style,
75 pub scroll: Option<ScrollStyle>,
76 pub block: Option<Block<'static>>,
77 pub button: Option<ButtonStyle>,
78
79 pub non_exhaustive: NonExhaustive,
80}
81
82#[derive(Debug, Clone)]
84pub struct MsgDialogState {
85 pub area: Rect,
88 pub inner: Rect,
91
92 pub active: Cell<bool>,
95 pub message_title: RefCell<String>,
98 pub message: RefCell<String>,
101
102 button: RefCell<ButtonState>,
104 paragraph: RefCell<ParagraphState>,
106}
107
108impl<'a> MsgDialog<'a> {
109 pub fn new() -> Self {
111 Self {
112 block: None,
113 style: Default::default(),
114 scroll_style: Default::default(),
115 button_style: Default::default(),
116 }
117 }
118
119 pub fn block(mut self, block: Block<'a>) -> Self {
121 self.block = Some(block);
122 self.block = self.block.map(|v| v.style(self.style));
123 self
124 }
125
126 pub fn styles(mut self, styles: MsgDialogStyle) -> Self {
128 self.style = styles.style;
129 if styles.scroll.is_some() {
130 self.scroll_style = styles.scroll;
131 }
132 if styles.block.is_some() {
133 self.block = styles.block;
134 }
135 if styles.button.is_some() {
136 self.button_style = styles.button;
137 }
138 self.block = self.block.map(|v| v.style(self.style));
139 self
140 }
141
142 pub fn style(mut self, style: impl Into<Style>) -> Self {
144 self.style = style.into();
145 self.block = self.block.map(|v| v.style(self.style));
146 self
147 }
148
149 pub fn scroll_style(mut self, style: ScrollStyle) -> Self {
151 self.scroll_style = Some(style);
152 self
153 }
154
155 pub fn button_style(mut self, style: ButtonStyle) -> Self {
157 self.button_style = Some(style);
158 self
159 }
160}
161
162impl Default for MsgDialogStyle {
163 fn default() -> Self {
164 Self {
165 style: Default::default(),
166 scroll: None,
167 block: None,
168 button: Default::default(),
169 non_exhaustive: NonExhaustive,
170 }
171 }
172}
173
174impl MsgDialogState {
175 pub fn new() -> Self {
176 Self::default()
177 }
178
179 pub fn new_active(title: impl Into<String>, msg: impl AsRef<str>) -> Self {
181 let zelf = Self::default();
182 zelf.set_active(true);
183 zelf.title(title);
184 zelf.append(msg.as_ref());
185 zelf
186 }
187
188 pub fn set_active(&self, active: bool) {
190 self.active.set(active);
191 self.build_focus().focus(&*self.paragraph.borrow());
192 self.paragraph.borrow_mut().set_line_offset(0);
193 self.paragraph.borrow_mut().set_col_offset(0);
194 }
195
196 pub fn active(&self) -> bool {
198 self.active.get()
199 }
200
201 pub fn clear(&self) {
203 self.active.set(false);
204 *self.message.borrow_mut() = Default::default();
205 }
206
207 pub fn title(&self, title: impl Into<String>) {
209 *self.message_title.borrow_mut() = title.into();
210 }
211
212 pub fn append(&self, msg: &str) {
214 self.set_active(true);
215 let mut message = self.message.borrow_mut();
216 if !message.is_empty() {
217 message.push('\n');
218 }
219 message.push_str(msg);
220 }
221}
222
223impl Default for MsgDialogState {
224 fn default() -> Self {
225 let s = Self {
226 active: Default::default(),
227 area: Default::default(),
228 inner: Default::default(),
229 message: Default::default(),
230 button: Default::default(),
231 paragraph: Default::default(),
232 message_title: Default::default(),
233 };
234 s.paragraph.borrow().focus.set(true);
235 s
236 }
237}
238
239impl MsgDialogState {
240 fn build_focus(&self) -> Focus {
241 let mut fb = FocusBuilder::default();
242 fb.widget(&*self.paragraph.borrow())
243 .widget(&*self.button.borrow());
244 fb.build()
245 }
246}
247
248impl<'a> StatefulWidget for &MsgDialog<'a> {
249 type State = MsgDialogState;
250
251 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
252 render_ref(self, area, buf, state);
253 }
254}
255
256impl StatefulWidget for MsgDialog<'_> {
257 type State = MsgDialogState;
258
259 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
260 render_ref(&self, area, buf, state);
261 }
262}
263
264fn render_ref(widget: &MsgDialog<'_>, area: Rect, buf: &mut Buffer, state: &mut MsgDialogState) {
265 if state.active.get() {
266 let mut block;
267 let title = state.message_title.borrow();
268 let block = if let Some(b) = &widget.block {
269 if !title.is_empty() {
270 block = b.clone().title(title.as_str());
271 &block
272 } else {
273 b
274 }
275 } else {
276 block = Block::bordered()
277 .style(widget.style)
278 .padding(Padding::new(1, 1, 1, 1));
279 if !title.is_empty() {
280 block = block.title(title.as_str());
281 }
282 &block
283 };
284
285 let l_dlg = layout_dialog(
286 area, block_padding2(block),
288 [Constraint::Length(10)],
289 0,
290 Flex::End,
291 );
292 state.area = l_dlg.area();
293 state.inner = l_dlg.widget_for(DialogItem::Inner);
294
295 reset_buf_area(state.area, buf);
296 block.render(state.area, buf);
297
298 {
299 let scroll = if let Some(style) = &widget.scroll_style {
300 Scroll::new().styles(style.clone())
301 } else {
302 Scroll::new().style(widget.style)
303 };
304
305 let message = state.message.borrow();
306 let mut lines = Vec::new();
307 for t in message.split('\n') {
308 lines.push(Line::from(t));
309 }
310 let text = Text::from(lines).alignment(Alignment::Center);
311 Paragraph::new(text).scroll(scroll).render(
312 l_dlg.widget_for(DialogItem::Content),
313 buf,
314 &mut state.paragraph.borrow_mut(),
315 );
316 }
317
318 Button::new("Ok")
319 .styles_opt(widget.button_style.clone())
320 .render(
321 l_dlg.widget_for(DialogItem::Button(0)),
322 buf,
323 &mut state.button.borrow_mut(),
324 );
325 }
326}
327
328impl HandleEvent<crossterm::event::Event, Dialog, Outcome> for MsgDialogState {
329 fn handle(&mut self, event: &crossterm::event::Event, _: Dialog) -> Outcome {
330 if self.active.get() {
331 let mut focus = self.build_focus();
332 let f = focus.handle(event, Regular);
333
334 let mut r = match self
335 .button
336 .borrow_mut()
337 .handle(event, KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))
338 {
339 ButtonOutcome::Pressed => {
340 self.clear();
341 self.active.set(false);
342 Outcome::Changed
343 }
344 v => v.into(),
345 };
346 r = r.or_else(|| self.paragraph.borrow_mut().handle(event, Regular));
347 r = r.or_else(|| match event {
348 ct_event!(keycode press Esc) => {
349 self.clear();
350 self.active.set(false);
351 Outcome::Changed
352 }
353 _ => Outcome::Continue,
354 });
355 max(max(Outcome::Unchanged, f), r)
357 } else {
358 Outcome::Continue
359 }
360 }
361}
362
363pub fn handle_dialog_events(
365 state: &mut MsgDialogState,
366 event: &crossterm::event::Event,
367) -> Outcome {
368 state.handle(event, Dialog)
369}