1use crate::_private::NonExhaustive;
44use crate::button::{Button, ButtonState, ButtonStyle};
45use crate::event::ButtonOutcome;
46use crate::layout::{DialogItem, LayoutOuter, layout_dialog};
47use crate::paragraph::{Paragraph, ParagraphState};
48use crate::text::HasScreenCursor;
49use crate::util::{block_padding2, reset_buf_area};
50use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
51use rat_event::{ConsumedEvent, Dialog, HandleEvent, Outcome, Regular, ct_event};
52use rat_focus::{Focus, FocusBuilder, FocusFlag, HasFocus};
53use rat_reloc::RelocatableState;
54use rat_scrolled::{Scroll, ScrollStyle};
55use ratatui::buffer::Buffer;
56use ratatui::layout::{Alignment, Constraint, Flex, Position, Rect, Size};
57use ratatui::style::Style;
58use ratatui::text::{Line, Text};
59use ratatui::widgets::{Block, Padding, StatefulWidget, Widget};
60use std::cell::{Cell, RefCell};
61use std::cmp::max;
62use std::fmt::Debug;
63
64#[derive(Debug, Default, Clone)]
66pub struct MsgDialog<'a> {
67 style: Style,
68 block: Option<Block<'a>>,
69 scroll_style: Option<ScrollStyle>,
70 button_style: Option<ButtonStyle>,
71
72 layout: LayoutOuter,
73}
74
75#[derive(Debug, Clone)]
77pub struct MsgDialogStyle {
78 pub style: Style,
79 pub block: Option<Block<'static>>,
80 pub border_style: Option<Style>,
81 pub title_style: Option<Style>,
82 pub scroll: Option<ScrollStyle>,
83
84 pub button: Option<ButtonStyle>,
85
86 pub non_exhaustive: NonExhaustive,
87}
88
89#[derive(Debug, Clone)]
91pub struct MsgDialogState {
92 pub area: Rect,
95 pub inner: Rect,
98
99 pub active: Cell<bool>,
102 pub message_title: RefCell<String>,
105 pub message: RefCell<String>,
108
109 button: RefCell<ButtonState>,
111 paragraph: RefCell<ParagraphState>,
113}
114
115impl<'a> MsgDialog<'a> {
116 pub fn new() -> Self {
118 Self::default()
119 }
120
121 pub fn block(mut self, block: Block<'a>) -> Self {
123 self.block = Some(block);
124 self.block = self.block.map(|v| v.style(self.style));
125 self
126 }
127
128 pub fn left(mut self, left: Constraint) -> Self {
130 self.layout = self.layout.left(left);
131 self
132 }
133
134 pub fn top(mut self, top: Constraint) -> Self {
136 self.layout = self.layout.top(top);
137 self
138 }
139
140 pub fn right(mut self, right: Constraint) -> Self {
142 self.layout = self.layout.right(right);
143 self
144 }
145
146 pub fn bottom(mut self, bottom: Constraint) -> Self {
148 self.layout = self.layout.bottom(bottom);
149 self
150 }
151
152 pub fn position(mut self, pos: Position) -> Self {
154 self.layout = self.layout.position(pos);
155 self
156 }
157
158 pub fn width(mut self, width: Constraint) -> Self {
160 self.layout = self.layout.width(width);
161 self
162 }
163
164 pub fn height(mut self, height: Constraint) -> Self {
166 self.layout = self.layout.height(height);
167 self
168 }
169
170 pub fn size(mut self, size: Size) -> Self {
172 self.layout = self.layout.size(size);
173 self
174 }
175
176 pub fn styles(mut self, styles: MsgDialogStyle) -> Self {
178 self.style = styles.style;
179 if styles.block.is_some() {
180 self.block = styles.block;
181 }
182 if let Some(border_style) = styles.border_style {
183 self.block = self.block.map(|v| v.border_style(border_style));
184 }
185 if let Some(title_style) = styles.title_style {
186 self.block = self.block.map(|v| v.title_style(title_style));
187 }
188 self.block = self.block.map(|v| v.style(self.style));
189
190 if styles.scroll.is_some() {
191 self.scroll_style = styles.scroll;
192 }
193
194 if styles.button.is_some() {
195 self.button_style = styles.button;
196 }
197
198 self
199 }
200
201 pub fn style(mut self, style: impl Into<Style>) -> Self {
203 self.style = style.into();
204 self.block = self.block.map(|v| v.style(self.style));
205 self
206 }
207
208 pub fn scroll_style(mut self, style: ScrollStyle) -> Self {
210 self.scroll_style = Some(style);
211 self
212 }
213
214 pub fn button_style(mut self, style: ButtonStyle) -> Self {
216 self.button_style = Some(style);
217 self
218 }
219}
220
221impl Default for MsgDialogStyle {
222 fn default() -> Self {
223 Self {
224 style: Default::default(),
225 block: Default::default(),
226 border_style: Default::default(),
227 title_style: Default::default(),
228 scroll: Default::default(),
229 button: Default::default(),
230 non_exhaustive: NonExhaustive,
231 }
232 }
233}
234
235impl HasFocus for MsgDialogState {
236 fn build(&self, _builder: &mut FocusBuilder) {
237 }
239
240 fn focus(&self) -> FocusFlag {
241 unimplemented!("not available")
242 }
243
244 fn area(&self) -> Rect {
245 unimplemented!("not available")
246 }
247}
248
249impl RelocatableState for MsgDialogState {
250 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
251 self.area.relocate(shift, clip);
252 self.inner.relocate(shift, clip);
253 self.button.borrow_mut().relocate(shift, clip);
254 self.paragraph.borrow_mut().relocate(shift, clip);
255 }
256}
257
258impl HasScreenCursor for MsgDialogState {
259 fn screen_cursor(&self) -> Option<(u16, u16)> {
260 None
261 }
262}
263
264impl MsgDialogState {
265 pub fn new() -> Self {
266 Self::default()
267 }
268
269 pub fn new_active(title: impl Into<String>, msg: impl AsRef<str>) -> Self {
271 let zelf = Self::default();
272 zelf.set_active(true);
273 zelf.title(title);
274 zelf.append(msg.as_ref());
275 zelf
276 }
277
278 pub fn set_active(&self, active: bool) {
280 self.active.set(active);
281 self.build_focus().focus(&*self.paragraph.borrow());
282 self.paragraph.borrow_mut().set_line_offset(0);
283 self.paragraph.borrow_mut().set_col_offset(0);
284 }
285
286 pub fn active(&self) -> bool {
288 self.active.get()
289 }
290
291 pub fn clear(&self) {
293 self.active.set(false);
294 *self.message.borrow_mut() = Default::default();
295 }
296
297 pub fn title(&self, title: impl Into<String>) {
299 *self.message_title.borrow_mut() = title.into();
300 }
301
302 pub fn append(&self, msg: &str) {
304 self.set_active(true);
305 let mut message = self.message.borrow_mut();
306 if !message.is_empty() {
307 message.push('\n');
308 }
309 message.push_str(msg);
310 }
311}
312
313impl Default for MsgDialogState {
314 fn default() -> Self {
315 let s = Self {
316 active: Default::default(),
317 area: Default::default(),
318 inner: Default::default(),
319 message: Default::default(),
320 button: Default::default(),
321 paragraph: Default::default(),
322 message_title: Default::default(),
323 };
324 s.paragraph.borrow().focus.set(true);
325 s
326 }
327}
328
329impl MsgDialogState {
330 fn build_focus(&self) -> Focus {
331 let mut fb = FocusBuilder::default();
332 fb.widget(&*self.paragraph.borrow())
333 .widget(&*self.button.borrow());
334 fb.build()
335 }
336}
337
338impl<'a> StatefulWidget for &MsgDialog<'a> {
339 type State = MsgDialogState;
340
341 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
342 render_ref(self, area, buf, state);
343 }
344}
345
346impl StatefulWidget for MsgDialog<'_> {
347 type State = MsgDialogState;
348
349 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
350 render_ref(&self, area, buf, state);
351 }
352}
353
354fn render_ref(widget: &MsgDialog<'_>, area: Rect, buf: &mut Buffer, state: &mut MsgDialogState) {
355 state.area = area;
356
357 if !state.active.get() {
358 return;
359 }
360
361 let mut block;
362 let title = state.message_title.borrow();
363 let block = if let Some(b) = &widget.block {
364 if !title.is_empty() {
365 block = b.clone().title(title.as_str());
366 &block
367 } else {
368 b
369 }
370 } else {
371 block = Block::bordered()
372 .style(widget.style)
373 .padding(Padding::new(1, 1, 1, 1));
374 if !title.is_empty() {
375 block = block.title(title.as_str());
376 }
377 &block
378 };
379
380 let l_dlg = layout_dialog(
381 area, block_padding2(block),
383 [Constraint::Length(10)],
384 0,
385 Flex::End,
386 );
387 state.area = l_dlg.area();
388 state.inner = l_dlg.widget_for(DialogItem::Inner);
389
390 reset_buf_area(state.area, buf);
391 block.render(state.area, buf);
392
393 {
394 let scroll = if let Some(style) = &widget.scroll_style {
395 Scroll::new().styles(style.clone())
396 } else {
397 Scroll::new().style(widget.style)
398 };
399
400 let message = state.message.borrow();
401 let mut lines = Vec::new();
402 for t in message.split('\n') {
403 lines.push(Line::from(t));
404 }
405 let text = Text::from(lines).alignment(Alignment::Center);
406 Paragraph::new(text).scroll(scroll).render(
407 l_dlg.widget_for(DialogItem::Content),
408 buf,
409 &mut state.paragraph.borrow_mut(),
410 );
411 }
412
413 Button::new("Ok")
414 .styles_opt(widget.button_style.clone())
415 .render(
416 l_dlg.widget_for(DialogItem::Button(0)),
417 buf,
418 &mut state.button.borrow_mut(),
419 );
420}
421
422impl HandleEvent<crossterm::event::Event, Dialog, Outcome> for MsgDialogState {
423 fn handle(&mut self, event: &crossterm::event::Event, _: Dialog) -> Outcome {
424 if self.active.get() {
425 let mut focus = self.build_focus();
426 let f = focus.handle(event, Regular);
427
428 let mut r = match self
429 .button
430 .borrow_mut()
431 .handle(event, KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))
432 {
433 ButtonOutcome::Pressed => {
434 self.clear();
435 self.active.set(false);
436 Outcome::Changed
437 }
438 v => v.into(),
439 };
440 r = r.or_else(|| self.paragraph.borrow_mut().handle(event, Regular));
441 r = r.or_else(|| match event {
442 ct_event!(keycode press Esc) => {
443 self.clear();
444 self.active.set(false);
445 Outcome::Changed
446 }
447 _ => Outcome::Continue,
448 });
449 max(max(Outcome::Unchanged, f), r)
451 } else {
452 Outcome::Continue
453 }
454 }
455}
456
457pub fn handle_dialog_events(
459 state: &mut MsgDialogState,
460 event: &crossterm::event::Event,
461) -> Outcome {
462 state.handle(event, Dialog)
463}