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