1use crate::_private::NonExhaustive;
6use crate::button::{Button, ButtonState, ButtonStyle};
7use crate::event::ButtonOutcome;
8use crate::layout::{layout_dialog, DialogItem};
9use crate::paragraph::{Paragraph, ParagraphState};
10use crate::util::{block_padding2, reset_buf_area};
11use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
12use rat_event::{ct_event, ConsumedEvent, Dialog, HandleEvent, Outcome, Regular};
13use rat_focus::{Focus, FocusBuilder};
14use rat_scrolled::{Scroll, ScrollStyle};
15use ratatui::buffer::Buffer;
16use ratatui::layout::{Alignment, Constraint, Flex, Rect};
17use ratatui::style::Style;
18use ratatui::text::{Line, Text};
19#[cfg(feature = "unstable-widget-ref")]
20use ratatui::widgets::StatefulWidgetRef;
21use ratatui::widgets::{Block, Padding, StatefulWidget, Widget};
22use std::cell::{Cell, RefCell};
23use std::cmp::max;
24use std::fmt::Debug;
25
26#[derive(Debug, Default, Clone)]
28pub struct MsgDialog<'a> {
29 style: Style,
30 scroll_style: Option<ScrollStyle>,
31 button_style: Option<ButtonStyle>,
32 block: Option<Block<'a>>,
33}
34
35#[derive(Debug, Clone)]
37pub struct MsgDialogStyle {
38 pub style: Style,
39 pub scroll: Option<ScrollStyle>,
40 pub block: Option<Block<'static>>,
41 pub button: Option<ButtonStyle>,
42
43 pub non_exhaustive: NonExhaustive,
44}
45
46#[derive(Debug, Clone)]
48pub struct MsgDialogState {
49 pub area: Rect,
52 pub inner: Rect,
55
56 pub active: Cell<bool>,
59 pub message_title: RefCell<String>,
62 pub message: RefCell<String>,
65
66 button: RefCell<ButtonState>,
68 paragraph: RefCell<ParagraphState>,
70}
71
72impl<'a> MsgDialog<'a> {
73 pub fn new() -> Self {
75 Self {
76 block: None,
77 style: Default::default(),
78 scroll_style: Default::default(),
79 button_style: Default::default(),
80 }
81 }
82
83 pub fn block(mut self, block: Block<'a>) -> Self {
85 self.block = Some(block);
86 self.block = self.block.map(|v| v.style(self.style));
87 self
88 }
89
90 pub fn styles(mut self, styles: MsgDialogStyle) -> Self {
92 self.style = styles.style;
93 if styles.scroll.is_some() {
94 self.scroll_style = styles.scroll;
95 }
96 if styles.block.is_some() {
97 self.block = styles.block;
98 }
99 if styles.button.is_some() {
100 self.button_style = styles.button;
101 }
102 self.block = self.block.map(|v| v.style(self.style));
103 self
104 }
105
106 pub fn style(mut self, style: impl Into<Style>) -> Self {
108 self.style = style.into();
109 self.block = self.block.map(|v| v.style(self.style));
110 self
111 }
112
113 pub fn scroll_style(mut self, style: ScrollStyle) -> Self {
115 self.scroll_style = Some(style);
116 self
117 }
118
119 pub fn button_style(mut self, style: ButtonStyle) -> Self {
121 self.button_style = Some(style);
122 self
123 }
124}
125
126impl Default for MsgDialogStyle {
127 fn default() -> Self {
128 Self {
129 style: Default::default(),
130 scroll: None,
131 block: None,
132 button: Default::default(),
133 non_exhaustive: NonExhaustive,
134 }
135 }
136}
137
138impl MsgDialogState {
139 pub fn new() -> Self {
140 Self::default()
141 }
142
143 pub fn set_active(&self, active: bool) {
145 self.active.set(active);
146 self.focus().focus(&*self.paragraph.borrow());
147 self.paragraph.borrow_mut().set_line_offset(0);
148 self.paragraph.borrow_mut().set_col_offset(0);
149 }
150
151 pub fn active(&self) -> bool {
153 self.active.get()
154 }
155
156 pub fn clear(&self) {
158 self.active.set(false);
159 *self.message.borrow_mut() = Default::default();
160 }
161
162 pub fn title(&self, title: impl Into<String>) {
164 *self.message_title.borrow_mut() = title.into();
165 }
166
167 pub fn append(&self, msg: &str) {
169 self.set_active(true);
170 let mut message = self.message.borrow_mut();
171 if !message.is_empty() {
172 message.push('\n');
173 }
174 message.push_str(msg);
175 }
176}
177
178impl Default for MsgDialogState {
179 fn default() -> Self {
180 let s = Self {
181 active: Default::default(),
182 area: Default::default(),
183 inner: Default::default(),
184 message: Default::default(),
185 button: Default::default(),
186 paragraph: Default::default(),
187 message_title: Default::default(),
188 };
189 s.paragraph.borrow().focus.set(true);
190 s
191 }
192}
193
194impl MsgDialogState {
195 fn focus(&self) -> Focus {
196 let mut fb = FocusBuilder::default();
197 fb.widget(&*self.paragraph.borrow())
198 .widget(&*self.button.borrow());
199 fb.build()
200 }
201}
202
203#[cfg(feature = "unstable-widget-ref")]
204impl<'a> StatefulWidgetRef for MsgDialog<'a> {
205 type State = MsgDialogState;
206
207 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
208 render_ref(self, area, buf, state);
209 }
210}
211
212impl StatefulWidget for MsgDialog<'_> {
213 type State = MsgDialogState;
214
215 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
216 render_ref(&self, area, buf, state);
217 }
218}
219
220fn render_ref(widget: &MsgDialog<'_>, area: Rect, buf: &mut Buffer, state: &mut MsgDialogState) {
221 if state.active.get() {
222 let mut block;
223 let title = state.message_title.borrow();
224 let block = if let Some(b) = &widget.block {
225 if !title.is_empty() {
226 block = b.clone().title(title.as_str());
227 &block
228 } else {
229 b
230 }
231 } else {
232 block = Block::bordered()
233 .style(widget.style)
234 .padding(Padding::new(1, 1, 1, 1));
235 if !title.is_empty() {
236 block = block.title(title.as_str());
237 }
238 &block
239 };
240
241 let l_dlg = layout_dialog(
242 area, block_padding2(block),
244 [Constraint::Length(10)],
245 0,
246 Flex::End,
247 );
248 state.area = l_dlg.area();
249 state.inner = l_dlg.widget_for(DialogItem::Inner);
250
251 reset_buf_area(state.area, buf);
252 block.render(state.area, buf);
253
254 {
255 let scroll = if let Some(style) = &widget.scroll_style {
256 Scroll::new().styles(style.clone())
257 } else {
258 Scroll::new().style(widget.style)
259 };
260
261 let message = state.message.borrow();
262 let mut lines = Vec::new();
263 for t in message.split('\n') {
264 lines.push(Line::from(t));
265 }
266 let text = Text::from(lines).alignment(Alignment::Center);
267 Paragraph::new(text).scroll(scroll).render(
268 l_dlg.widget_for(DialogItem::Content),
269 buf,
270 &mut state.paragraph.borrow_mut(),
271 );
272 }
273
274 Button::new("Ok")
275 .styles_opt(widget.button_style.clone())
276 .render(
277 l_dlg.widget_for(DialogItem::Button(0)),
278 buf,
279 &mut state.button.borrow_mut(),
280 );
281 }
282}
283
284impl HandleEvent<crossterm::event::Event, Dialog, Outcome> for MsgDialogState {
285 fn handle(&mut self, event: &crossterm::event::Event, _: Dialog) -> Outcome {
286 if self.active.get() {
287 let mut focus = self.focus();
288 let f = focus.handle(event, Regular);
289
290 let mut r = match self
291 .button
292 .borrow_mut()
293 .handle(event, KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE))
294 {
295 ButtonOutcome::Pressed => {
296 self.clear();
297 self.active.set(false);
298 Outcome::Changed
299 }
300 v => v.into(),
301 };
302 r = r.or_else(|| self.paragraph.borrow_mut().handle(event, Regular));
303 r = r.or_else(|| match event {
304 ct_event!(keycode press Esc) => {
305 self.clear();
306 self.active.set(false);
307 Outcome::Changed
308 }
309 _ => Outcome::Continue,
310 });
311 max(max(Outcome::Unchanged, f), r)
313 } else {
314 Outcome::Continue
315 }
316 }
317}
318
319pub fn handle_dialog_events(
321 state: &mut MsgDialogState,
322 event: &crossterm::event::Event,
323) -> Outcome {
324 state.handle(event, Dialog)
325}