1use crate::_private::NonExhaustive;
5use crate::button::{Button, ButtonState, ButtonStyle};
6use crate::event::{
7 ButtonOutcome, ConsumedEvent, Dialog, HandleEvent, Outcome, Regular, ct_event, flow,
8};
9use crate::focus::{FocusBuilder, FocusFlag, HasFocus};
10use crate::layout::{DialogItem, LayoutOuter};
11use crate::text::HasScreenCursor;
12use crate::util::{block_padding2, reset_buf_area};
13use rat_event::MouseOnly;
14use rat_reloc::RelocatableState;
15use ratatui_core::buffer::Buffer;
16use ratatui_core::layout::{Constraint, Flex, Position, Rect, Size};
17use ratatui_core::style::Style;
18use ratatui_core::widgets::{StatefulWidget, Widget};
19use ratatui_crossterm::crossterm::event::Event;
20use ratatui_widgets::block::Block;
21use ratatui_widgets::borders::BorderType;
22
23#[derive(Debug, Clone, Default)]
28pub struct DialogFrame<'a> {
29 block: Block<'a>,
30 button_style: ButtonStyle,
31 layout: LayoutOuter,
32 ok_text: &'a str,
33 no_cancel: bool,
34 cancel_text: &'a str,
35}
36
37#[derive(Debug, Clone)]
39pub struct DialogFrameStyle {
40 pub style: Style,
41 pub block: Option<Block<'static>>,
42 pub border_style: Option<Style>,
43 pub title_style: Option<Style>,
44 pub button_style: Option<ButtonStyle>,
45 pub layout: Option<LayoutOuter>,
46 pub ok_text: Option<&'static str>,
47 pub no_cancel: Option<bool>,
48 pub cancel_text: Option<&'static str>,
49 pub non_exhaustive: NonExhaustive,
50}
51
52impl Default for DialogFrameStyle {
53 fn default() -> Self {
54 Self {
55 style: Default::default(),
56 block: Default::default(),
57 border_style: Default::default(),
58 title_style: Default::default(),
59 button_style: Default::default(),
60 layout: Default::default(),
61 ok_text: Default::default(),
62 no_cancel: Default::default(),
63 cancel_text: Default::default(),
64 non_exhaustive: NonExhaustive,
65 }
66 }
67}
68
69#[derive(Debug, Clone)]
70pub struct DialogFrameState {
71 pub area: Rect,
74 pub widget_area: Rect,
77
78 pub ok: ButtonState,
80 pub no_cancel: bool,
82 pub cancel: ButtonState,
84
85 pub non_exhaustive: NonExhaustive,
86}
87
88impl<'a> DialogFrame<'a> {
89 pub fn new() -> Self {
90 Self {
91 block: Block::bordered().border_type(BorderType::Plain),
92 button_style: Default::default(),
93 layout: LayoutOuter::new()
94 .left(Constraint::Percentage(19))
95 .top(Constraint::Length(3))
96 .right(Constraint::Percentage(19))
97 .bottom(Constraint::Length(3)),
98 ok_text: "Ok",
99 no_cancel: false,
100 cancel_text: "Cancel",
101 }
102 }
103
104 pub fn styles(mut self, styles: DialogFrameStyle) -> Self {
105 if let Some(block) = styles.block {
106 self.block = block;
107 }
108 self.block = self.block.style(styles.style);
109 if let Some(border_style) = styles.border_style {
110 self.block = self.block.border_style(border_style);
111 }
112 if let Some(title_style) = styles.title_style {
113 self.block = self.block.title_style(title_style);
114 }
115 if let Some(button_style) = styles.button_style {
116 self.button_style = button_style;
117 }
118 if let Some(layout) = styles.layout {
119 self.layout = layout;
120 }
121 if let Some(ok_text) = styles.ok_text {
122 self.ok_text = ok_text;
123 }
124 if let Some(no_cancel) = styles.no_cancel {
125 self.no_cancel = no_cancel;
126 }
127 if let Some(cancel_text) = styles.cancel_text {
128 self.cancel_text = cancel_text;
129 }
130 self
131 }
132
133 pub fn style(mut self, style: Style) -> Self {
135 self.block = self.block.style(style);
136 self
137 }
138
139 pub fn block(mut self, block: Block<'a>) -> Self {
141 self.block = block;
142 self
143 }
144
145 pub fn border_style(mut self, style: Style) -> Self {
147 self.block = self.block.border_style(style);
148 self
149 }
150
151 pub fn title_style(mut self, style: Style) -> Self {
153 self.block = self.block.title_style(style);
154 self
155 }
156
157 pub fn button_style(mut self, style: ButtonStyle) -> Self {
159 self.button_style = style;
160 self
161 }
162
163 pub fn ok_text(mut self, str: &'a str) -> Self {
165 self.ok_text = str;
166 self
167 }
168
169 pub fn no_cancel(mut self) -> Self {
171 self.no_cancel = true;
172 self
173 }
174
175 pub fn cancel_text(mut self, str: &'a str) -> Self {
177 self.cancel_text = str;
178 self
179 }
180
181 pub fn left(mut self, left: Constraint) -> Self {
183 self.layout = self.layout.left(left);
184 self
185 }
186
187 pub fn top(mut self, top: Constraint) -> Self {
189 self.layout = self.layout.top(top);
190 self
191 }
192
193 pub fn right(mut self, right: Constraint) -> Self {
195 self.layout = self.layout.right(right);
196 self
197 }
198
199 pub fn bottom(mut self, bottom: Constraint) -> Self {
201 self.layout = self.layout.bottom(bottom);
202 self
203 }
204
205 pub fn position(mut self, pos: Position) -> Self {
207 self.layout = self.layout.position(pos);
208 self
209 }
210
211 pub fn width(mut self, width: Constraint) -> Self {
213 self.layout = self.layout.width(width);
214 self
215 }
216
217 pub fn height(mut self, height: Constraint) -> Self {
219 self.layout = self.layout.height(height);
220 self
221 }
222
223 pub fn size(mut self, size: Size) -> Self {
225 self.layout = self.layout.size(size);
226 self
227 }
228
229 pub fn layout_size(&self, area: Rect) -> Rect {
232 let l_dlg = if self.no_cancel {
233 self.layout.layout_dialog(
234 area,
235 block_padding2(&self.block),
236 [Constraint::Length(10)],
237 1,
238 Flex::End,
239 )
240 } else {
241 self.layout.layout_dialog(
242 area,
243 block_padding2(&self.block),
244 [Constraint::Length(12), Constraint::Length(10)],
245 1,
246 Flex::End,
247 )
248 };
249 l_dlg.widget_for(DialogItem::Content)
250 }
251}
252
253impl<'a> StatefulWidget for DialogFrame<'a> {
254 type State = DialogFrameState;
255
256 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
257 let l_dlg = if self.no_cancel {
258 self.layout.layout_dialog(
259 area,
260 block_padding2(&self.block),
261 [Constraint::Length(10)],
262 1,
263 Flex::End,
264 )
265 } else {
266 self.layout.layout_dialog(
267 area,
268 block_padding2(&self.block),
269 [Constraint::Length(12), Constraint::Length(10)],
270 1,
271 Flex::End,
272 )
273 };
274 state.area = l_dlg.area();
275 state.widget_area = l_dlg.widget_for(DialogItem::Content);
276 state.no_cancel = self.no_cancel;
277
278 reset_buf_area(l_dlg.area(), buf);
279 self.block.render(state.area, buf);
280
281 if self.no_cancel {
282 Button::new(self.ok_text).styles(self.button_style).render(
283 l_dlg.widget_for(DialogItem::Button(0)),
284 buf,
285 &mut state.ok,
286 );
287 } else {
288 Button::new(self.cancel_text)
289 .styles(self.button_style.clone())
290 .render(
291 l_dlg.widget_for(DialogItem::Button(0)),
292 buf,
293 &mut state.cancel,
294 );
295 Button::new(self.ok_text).styles(self.button_style).render(
296 l_dlg.widget_for(DialogItem::Button(1)),
297 buf,
298 &mut state.ok,
299 );
300 }
301 }
302}
303
304impl Default for DialogFrameState {
305 fn default() -> Self {
306 let z = Self {
307 area: Default::default(),
308 widget_area: Default::default(),
309 ok: Default::default(),
310 no_cancel: Default::default(),
311 cancel: Default::default(),
312 non_exhaustive: NonExhaustive,
313 };
314 z.ok.focus.set(true);
315 z
316 }
317}
318
319impl HasFocus for DialogFrameState {
320 fn build(&self, builder: &mut FocusBuilder) {
321 builder.widget(&self.ok);
322 if !self.no_cancel {
323 builder.widget(&self.cancel);
324 }
325 }
326
327 fn focus(&self) -> FocusFlag {
328 unimplemented!()
329 }
330
331 fn area(&self) -> Rect {
332 unimplemented!()
333 }
334}
335
336impl HasScreenCursor for DialogFrameState {
337 fn screen_cursor(&self) -> Option<(u16, u16)> {
338 None
339 }
340}
341
342impl RelocatableState for DialogFrameState {
343 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
344 self.area.relocate(shift, clip);
345 self.widget_area.relocate(shift, clip);
346 self.ok.relocate(shift, clip);
347 self.cancel.relocate(shift, clip);
348 }
349}
350
351impl DialogFrameState {
352 pub fn new() -> Self {
353 Self::default()
354 }
355
356 pub fn named(_name: &str) -> Self {
357 Self::default()
358 }
359}
360
361pub enum DialogOutcome {
363 Continue,
366 Unchanged,
369 Changed,
372 Ok,
374 Cancel,
376}
377
378impl ConsumedEvent for DialogOutcome {
379 fn is_consumed(&self) -> bool {
380 !matches!(self, DialogOutcome::Continue)
381 }
382}
383
384impl From<DialogOutcome> for Outcome {
385 fn from(value: DialogOutcome) -> Self {
386 match value {
387 DialogOutcome::Continue => Outcome::Continue,
388 DialogOutcome::Unchanged => Outcome::Unchanged,
389 DialogOutcome::Changed => Outcome::Changed,
390 DialogOutcome::Ok => Outcome::Changed,
391 DialogOutcome::Cancel => Outcome::Changed,
392 }
393 }
394}
395
396impl From<Outcome> for DialogOutcome {
397 fn from(value: Outcome) -> Self {
398 match value {
399 Outcome::Continue => DialogOutcome::Continue,
400 Outcome::Unchanged => DialogOutcome::Unchanged,
401 Outcome::Changed => DialogOutcome::Changed,
402 }
403 }
404}
405
406impl HandleEvent<Event, Dialog, DialogOutcome> for DialogFrameState {
407 fn handle(&mut self, event: &Event, _: Dialog) -> DialogOutcome {
408 flow!({
409 if !self.no_cancel {
410 match self.cancel.handle(event, Regular) {
411 ButtonOutcome::Pressed => DialogOutcome::Cancel,
412 r => Outcome::from(r).into(),
413 }
414 } else {
415 DialogOutcome::Continue
416 }
417 });
418 flow!(match self.ok.handle(event, Regular) {
419 ButtonOutcome::Pressed => {
420 DialogOutcome::Ok
421 }
422 r => Outcome::from(r).into(),
423 });
424
425 flow!(match event {
426 ct_event!(keycode press Esc) if !self.no_cancel => {
427 DialogOutcome::Cancel
428 }
429 ct_event!(keycode press Enter) | ct_event!(keycode press F(12)) => {
430 DialogOutcome::Ok
431 }
432 _ => DialogOutcome::Unchanged,
433 });
434
435 DialogOutcome::Unchanged
436 }
437}
438
439impl HandleEvent<Event, MouseOnly, DialogOutcome> for DialogFrameState {
440 fn handle(&mut self, event: &Event, _: MouseOnly) -> DialogOutcome {
441 flow!({
442 if !self.no_cancel {
443 match self.cancel.handle(event, MouseOnly) {
444 ButtonOutcome::Pressed => DialogOutcome::Cancel,
445 r => Outcome::from(r).into(),
446 }
447 } else {
448 DialogOutcome::Continue
449 }
450 });
451 flow!(match self.ok.handle(event, MouseOnly) {
452 ButtonOutcome::Pressed => {
453 DialogOutcome::Ok
454 }
455 r => Outcome::from(r).into(),
456 });
457
458 DialogOutcome::Unchanged
459 }
460}
461
462pub fn handle_events(state: &mut DialogFrameState, _focus: bool, event: &Event) -> DialogOutcome {
466 HandleEvent::handle(state, event, Dialog)
467}
468
469pub fn handle_mouse_events(state: &mut DialogFrameState, event: &Event) -> DialogOutcome {
471 HandleEvent::handle(state, event, MouseOnly)
472}