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::util::{block_padding2, fill_buf_area};
12use crossterm::event::Event;
13use ratatui::buffer::Buffer;
14use ratatui::layout::{Constraint, Flex, Position, Rect, Size};
15use ratatui::style::Style;
16use ratatui::widgets::{Block, BorderType, StatefulWidget, Widget};
17
18#[derive(Debug, Default)]
23pub struct DialogFrame<'a> {
24 style: Style,
25 block: Block<'a>,
26 button_style: ButtonStyle,
27 layout: LayoutOuter,
28 ok_text: &'a str,
29 no_cancel: bool,
30 cancel_text: &'a str,
31}
32
33#[derive(Debug, Clone)]
35pub struct DialogFrameStyle {
36 pub style: Style,
37 pub block: Option<Block<'static>>,
38 pub button_style: Option<ButtonStyle>,
39 pub layout: Option<LayoutOuter>,
40 pub ok_text: Option<&'static str>,
41 pub no_cancel: Option<bool>,
42 pub cancel_text: Option<&'static str>,
43 pub non_exhaustive: NonExhaustive,
44}
45
46impl Default for DialogFrameStyle {
47 fn default() -> Self {
48 Self {
49 style: Default::default(),
50 block: Default::default(),
51 button_style: Default::default(),
52 layout: Default::default(),
53 ok_text: Default::default(),
54 no_cancel: Default::default(),
55 cancel_text: Default::default(),
56 non_exhaustive: NonExhaustive,
57 }
58 }
59}
60
61#[derive(Debug, Clone)]
62pub struct DialogFrameState {
63 pub area: Rect,
66 pub widget_area: Rect,
69
70 pub ok: ButtonState,
72 pub no_cancel: bool,
74 pub cancel: ButtonState,
76}
77
78impl<'a> DialogFrame<'a> {
79 pub fn new() -> Self {
80 Self {
81 style: Default::default(),
82 block: Block::bordered().border_type(BorderType::Plain),
83 button_style: Default::default(),
84 layout: LayoutOuter::new()
85 .left(Constraint::Percentage(19))
86 .top(Constraint::Length(3))
87 .right(Constraint::Percentage(19))
88 .bottom(Constraint::Length(3)),
89 ok_text: "Ok",
90 no_cancel: false,
91 cancel_text: "Cancel",
92 }
93 }
94
95 pub fn styles(mut self, styles: DialogFrameStyle) -> Self {
96 self.style = styles.style;
97 if let Some(block) = styles.block {
98 self.block = block;
99 }
100 if let Some(button_style) = styles.button_style {
101 self.button_style = button_style;
102 }
103 if let Some(layout) = styles.layout {
104 self.layout = layout;
105 }
106 if let Some(ok_text) = styles.ok_text {
107 self.ok_text = ok_text;
108 }
109 if let Some(no_cancel) = styles.no_cancel {
110 self.no_cancel = no_cancel;
111 }
112 if let Some(cancel_text) = styles.cancel_text {
113 self.cancel_text = cancel_text;
114 }
115 self
116 }
117
118 pub fn style(mut self, style: Style) -> Self {
120 self.style = style;
121 self
122 }
123
124 pub fn block(mut self, block: Block<'a>) -> Self {
126 self.block = block;
127 self
128 }
129
130 pub fn button_style(mut self, style: ButtonStyle) -> Self {
132 self.button_style = style;
133 self
134 }
135
136 pub fn ok_text(mut self, str: &'a str) -> Self {
138 self.ok_text = str;
139 self
140 }
141
142 pub fn no_cancel(mut self) -> Self {
144 self.no_cancel = true;
145 self
146 }
147
148 pub fn cancel_text(mut self, str: &'a str) -> Self {
150 self.cancel_text = str;
151 self
152 }
153
154 pub fn left(mut self, left: Constraint) -> Self {
156 self.layout = self.layout.left(left);
157 self
158 }
159
160 pub fn top(mut self, top: Constraint) -> Self {
162 self.layout = self.layout.top(top);
163 self
164 }
165
166 pub fn right(mut self, right: Constraint) -> Self {
168 self.layout = self.layout.right(right);
169 self
170 }
171
172 pub fn bottom(mut self, bottom: Constraint) -> Self {
174 self.layout = self.layout.bottom(bottom);
175 self
176 }
177
178 pub fn position(mut self, pos: Position) -> Self {
180 self.layout = self.layout.position(pos);
181 self
182 }
183
184 pub fn width(mut self, width: Constraint) -> Self {
186 self.layout = self.layout.width(width);
187 self
188 }
189
190 pub fn height(mut self, height: Constraint) -> Self {
192 self.layout = self.layout.height(height);
193 self
194 }
195
196 pub fn size(mut self, size: Size) -> Self {
198 self.layout = self.layout.size(size);
199 self
200 }
201}
202
203impl<'a> StatefulWidget for DialogFrame<'a> {
204 type State = DialogFrameState;
205
206 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
207 state.area = self.layout.layout(area);
208 state.no_cancel = self.no_cancel;
209
210 let l_dlg = if self.no_cancel {
211 layout_dialog(
212 state.area,
213 block_padding2(&self.block),
214 [Constraint::Length(10)],
215 1,
216 Flex::End,
217 )
218 } else {
219 layout_dialog(
220 state.area,
221 block_padding2(&self.block),
222 [Constraint::Length(12), Constraint::Length(10)],
223 1,
224 Flex::End,
225 )
226 };
227 state.widget_area = l_dlg.widget_for(DialogItem::Content);
228
229 fill_buf_area(buf, l_dlg.area(), " ", self.style);
230 self.block.render(state.area, buf);
231
232 if self.no_cancel {
233 Button::new(self.ok_text).styles(self.button_style).render(
234 l_dlg.widget_for(DialogItem::Button(0)),
235 buf,
236 &mut state.ok,
237 );
238 } else {
239 Button::new(self.cancel_text)
240 .styles(self.button_style.clone())
241 .render(
242 l_dlg.widget_for(DialogItem::Button(0)),
243 buf,
244 &mut state.cancel,
245 );
246 Button::new(self.ok_text).styles(self.button_style).render(
247 l_dlg.widget_for(DialogItem::Button(1)),
248 buf,
249 &mut state.ok,
250 );
251 }
252 }
253}
254
255impl Default for DialogFrameState {
256 fn default() -> Self {
257 let z = Self {
258 area: Default::default(),
259 widget_area: Default::default(),
260 ok: Default::default(),
261 no_cancel: Default::default(),
262 cancel: Default::default(),
263 };
264 z.ok.focus.set(true);
265 z
266 }
267}
268
269impl HasFocus for DialogFrameState {
270 fn build(&self, builder: &mut FocusBuilder) {
271 builder.widget(&self.ok);
272 if !self.no_cancel {
273 builder.widget(&self.cancel);
274 }
275 }
276
277 fn focus(&self) -> FocusFlag {
278 unimplemented!()
279 }
280
281 fn area(&self) -> Rect {
282 unimplemented!()
283 }
284}
285
286impl DialogFrameState {
287 pub fn new() -> Self {
288 Self::default()
289 }
290}
291
292pub enum DialogOutcome {
294 Continue,
297 Unchanged,
300 Changed,
303 Ok,
305 Cancel,
307}
308
309impl ConsumedEvent for DialogOutcome {
310 fn is_consumed(&self) -> bool {
311 !matches!(self, DialogOutcome::Continue)
312 }
313}
314
315impl From<DialogOutcome> for Outcome {
316 fn from(value: DialogOutcome) -> Self {
317 match value {
318 DialogOutcome::Continue => Outcome::Continue,
319 DialogOutcome::Unchanged => Outcome::Unchanged,
320 DialogOutcome::Changed => Outcome::Changed,
321 DialogOutcome::Ok => Outcome::Changed,
322 DialogOutcome::Cancel => Outcome::Changed,
323 }
324 }
325}
326
327impl From<Outcome> for DialogOutcome {
328 fn from(value: Outcome) -> Self {
329 match value {
330 Outcome::Continue => DialogOutcome::Continue,
331 Outcome::Unchanged => DialogOutcome::Unchanged,
332 Outcome::Changed => DialogOutcome::Changed,
333 }
334 }
335}
336
337impl<'a> HandleEvent<Event, Dialog, DialogOutcome> for DialogFrameState {
338 fn handle(&mut self, event: &Event, _: Dialog) -> DialogOutcome {
339 flow!({
340 if !self.no_cancel {
341 match self.cancel.handle(event, Regular) {
342 ButtonOutcome::Pressed => DialogOutcome::Cancel,
343 r => Outcome::from(r).into(),
344 }
345 } else {
346 DialogOutcome::Continue
347 }
348 });
349 flow!(match self.ok.handle(event, Regular) {
350 ButtonOutcome::Pressed => {
351 DialogOutcome::Ok
352 }
353 r => Outcome::from(r).into(),
354 });
355
356 flow!(match event {
357 ct_event!(keycode press Esc) if !self.no_cancel => {
358 DialogOutcome::Cancel
359 }
360 ct_event!(keycode press Enter) | ct_event!(keycode press F(12)) => {
361 DialogOutcome::Ok
362 }
363 _ => DialogOutcome::Unchanged,
364 });
365
366 DialogOutcome::Unchanged
367 }
368}