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