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::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, Rect};
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 scroll_style: Option<ScrollStyle>,
69 button_style: Option<ButtonStyle>,
70 block: Option<Block<'a>>,
71}
72
73#[derive(Debug, Clone)]
75pub struct MsgDialogStyle {
76 pub style: Style,
77 pub scroll: Option<ScrollStyle>,
78 pub block: Option<Block<'static>>,
79 pub button: Option<ButtonStyle>,
80
81 pub non_exhaustive: NonExhaustive,
82}
83
84#[derive(Debug, Clone)]
86pub struct MsgDialogState {
87 pub area: Rect,
90 pub inner: Rect,
93
94 pub active: Cell<bool>,
97 pub message_title: RefCell<String>,
100 pub message: RefCell<String>,
103
104 button: RefCell<ButtonState>,
106 paragraph: RefCell<ParagraphState>,
108}
109
110impl<'a> MsgDialog<'a> {
111 pub fn new() -> Self {
113 Self {
114 block: None,
115 style: Default::default(),
116 scroll_style: Default::default(),
117 button_style: Default::default(),
118 }
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 styles(mut self, styles: MsgDialogStyle) -> Self {
130 self.style = styles.style;
131 if styles.scroll.is_some() {
132 self.scroll_style = styles.scroll;
133 }
134 if styles.block.is_some() {
135 self.block = styles.block;
136 }
137 if styles.button.is_some() {
138 self.button_style = styles.button;
139 }
140 self.block = self.block.map(|v| v.style(self.style));
141 self
142 }
143
144 pub fn style(mut self, style: impl Into<Style>) -> Self {
146 self.style = style.into();
147 self.block = self.block.map(|v| v.style(self.style));
148 self
149 }
150
151 pub fn scroll_style(mut self, style: ScrollStyle) -> Self {
153 self.scroll_style = Some(style);
154 self
155 }
156
157 pub fn button_style(mut self, style: ButtonStyle) -> Self {
159 self.button_style = Some(style);
160 self
161 }
162}
163
164impl Default for MsgDialogStyle {
165 fn default() -> Self {
166 Self {
167 style: Default::default(),
168 scroll: None,
169 block: None,
170 button: Default::default(),
171 non_exhaustive: NonExhaustive,
172 }
173 }
174}
175
176impl HasFocus for MsgDialogState {
177 fn build(&self, _builder: &mut FocusBuilder) {
178 }
180
181 fn focus(&self) -> FocusFlag {
182 unimplemented!("not available")
183 }
184
185 fn area(&self) -> Rect {
186 unimplemented!("not available")
187 }
188}
189
190impl RelocatableState for MsgDialogState {
191 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
192 self.area.relocate(shift, clip);
193 self.inner.relocate(shift, clip);
194 self.button.borrow_mut().relocate(shift, clip);
195 self.paragraph.borrow_mut().relocate(shift, clip);
196 }
197}
198
199impl HasScreenCursor for MsgDialogState {
200 fn screen_cursor(&self) -> Option<(u16, u16)> {
201 None
202 }
203}
204
205impl MsgDialogState {
206 pub fn new() -> Self {
207 Self::default()
208 }
209
210 pub fn new_active(title: impl Into<String>, msg: impl AsRef<str>) -> Self {
212 let zelf = Self::default();
213 zelf.set_active(true);
214 zelf.title(title);
215 zelf.append(msg.as_ref());
216 zelf
217 }
218
219 pub fn set_active(&self, active: bool) {
221 self.active.set(active);
222 self.build_focus().focus(&*self.paragraph.borrow());
223 self.paragraph.borrow_mut().set_line_offset(0);
224 self.paragraph.borrow_mut().set_col_offset(0);
225 }
226
227 pub fn active(&self) -> bool {
229 self.active.get()
230 }
231
232 pub fn clear(&self) {
234 self.active.set(false);
235 *self.message.borrow_mut() = Default::default();
236 }
237
238 pub fn title(&self, title: impl Into<String>) {
240 *self.message_title.borrow_mut() = title.into();
241 }
242
243 pub fn append(&self, msg: &str) {
245 self.set_active(true);
246 let mut message = self.message.borrow_mut();
247 if !message.is_empty() {
248 message.push('\n');
249 }
250 message.push_str(msg);
251 }
252}
253
254impl Default for MsgDialogState {
255 fn default() -> Self {
256 let s = Self {
257 active: Default::default(),
258 area: Default::default(),
259 inner: Default::default(),
260 message: Default::default(),
261 button: Default::default(),
262 paragraph: Default::default(),
263 message_title: Default::default(),
264 };
265 s.paragraph.borrow().focus.set(true);
266 s
267 }
268}
269
270impl MsgDialogState {
271 fn build_focus(&self) -> Focus {
272 let mut fb = FocusBuilder::default();
273 fb.widget(&*self.paragraph.borrow())
274 .widget(&*self.button.borrow());
275 fb.build()
276 }
277}
278
279impl<'a> StatefulWidget for &MsgDialog<'a> {
280 type State = MsgDialogState;
281
282 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
283 render_ref(self, area, buf, state);
284 }
285}
286
287impl StatefulWidget for MsgDialog<'_> {
288 type State = MsgDialogState;
289
290 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
291 render_ref(&self, area, buf, state);
292 }
293}
294
295fn render_ref(widget: &MsgDialog<'_>, area: Rect, buf: &mut Buffer, state: &mut MsgDialogState) {
296 state.area = area;
297
298 if !state.active.get() {
299 return;
300 }
301
302 let mut block;
303 let title = state.message_title.borrow();
304 let block = if let Some(b) = &widget.block {
305 if !title.is_empty() {
306 block = b.clone().title(title.as_str());
307 &block
308 } else {
309 b
310 }
311 } else {
312 block = Block::bordered()
313 .style(widget.style)
314 .padding(Padding::new(1, 1, 1, 1));
315 if !title.is_empty() {
316 block = block.title(title.as_str());
317 }
318 &block
319 };
320
321 let l_dlg = layout_dialog(
322 area, block_padding2(block),
324 [Constraint::Length(10)],
325 0,
326 Flex::End,
327 );
328 state.area = l_dlg.area();
329 state.inner = l_dlg.widget_for(DialogItem::Inner);
330
331 reset_buf_area(state.area, buf);
332 block.render(state.area, buf);
333
334 {
335 let scroll = if let Some(style) = &widget.scroll_style {
336 Scroll::new().styles(style.clone())
337 } else {
338 Scroll::new().style(widget.style)
339 };
340
341 let message = state.message.borrow();
342 let mut lines = Vec::new();
343 for t in message.split('\n') {
344 lines.push(Line::from(t));
345 }
346 let text = Text::from(lines).alignment(Alignment::Center);
347 Paragraph::new(text).scroll(scroll).render(
348 l_dlg.widget_for(DialogItem::Content),
349 buf,
350 &mut state.paragraph.borrow_mut(),
351 );
352 }
353
354 Button::new("Ok")
355 .styles_opt(widget.button_style.clone())
356 .render(
357 l_dlg.widget_for(DialogItem::Button(0)),
358 buf,
359 &mut state.button.borrow_mut(),
360 );
361}
362
363impl HandleEvent<crossterm::event::Event, Dialog, Outcome> for MsgDialogState {
364 fn handle(&mut self, event: &crossterm::event::Event, _: Dialog) -> Outcome {
365 if self.active.get() {
366 let mut focus = self.build_focus();
367 let f = focus.handle(event, Regular);
368
369 let mut r = match self
370 .button
371 .borrow_mut()
372 .handle(event, KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))
373 {
374 ButtonOutcome::Pressed => {
375 self.clear();
376 self.active.set(false);
377 Outcome::Changed
378 }
379 v => v.into(),
380 };
381 r = r.or_else(|| self.paragraph.borrow_mut().handle(event, Regular));
382 r = r.or_else(|| match event {
383 ct_event!(keycode press Esc) => {
384 self.clear();
385 self.active.set(false);
386 Outcome::Changed
387 }
388 _ => Outcome::Continue,
389 });
390 max(max(Outcome::Unchanged, f), r)
392 } else {
393 Outcome::Continue
394 }
395 }
396}
397
398pub fn handle_dialog_events(
400 state: &mut MsgDialogState,
401 event: &crossterm::event::Event,
402) -> Outcome {
403 state.handle(event, Dialog)
404}