1use crate::_private::NonExhaustive;
6use crossterm::event::Event;
7use rat_widget::button::{Button, ButtonState, ButtonStyle};
8use rat_widget::event::{
9 ButtonOutcome, ConsumedEvent, Dialog, HandleEvent, Outcome, Regular, ct_event, flow,
10};
11use rat_widget::focus::{FocusBuilder, FocusFlag, HasFocus};
12use rat_widget::layout::{DialogItem, LayoutOuter, layout_dialog};
13use rat_widget::util::{block_padding2, fill_buf_area};
14use ratatui::buffer::Buffer;
15use ratatui::layout::{Constraint, Flex, Position, Rect, Size};
16use ratatui::style::Style;
17use ratatui::widgets::{Block, BorderType, StatefulWidget, Widget};
18
19#[derive(Debug, Default)]
24pub struct BaseDialog<'a> {
25 style: Style,
26 block: Block<'a>,
27 button_style: ButtonStyle,
28 layout: LayoutOuter,
29 ok_text: &'a str,
30 cancel_text: &'a str,
31}
32
33#[derive(Debug, Clone)]
35pub struct BaseDialogStyle {
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 cancel_text: Option<&'static str>,
42 pub non_exhaustive: NonExhaustive,
43}
44
45impl Default for BaseDialogStyle {
46 fn default() -> Self {
47 Self {
48 style: Default::default(),
49 block: Default::default(),
50 button_style: Default::default(),
51 layout: Default::default(),
52 ok_text: Default::default(),
53 cancel_text: Default::default(),
54 non_exhaustive: NonExhaustive,
55 }
56 }
57}
58
59#[derive(Debug, Clone)]
60pub struct BaseDialogState {
61 pub area: Rect,
64 pub widget_area: Rect,
67
68 pub ok: ButtonState,
70 pub cancel: ButtonState,
72}
73
74impl<'a> BaseDialog<'a> {
75 pub fn new() -> Self {
76 Self {
77 style: Default::default(),
78 block: Block::bordered().border_type(BorderType::Plain),
79 button_style: Default::default(),
80 layout: LayoutOuter::new()
81 .left(Constraint::Percentage(19))
82 .top(Constraint::Length(3))
83 .right(Constraint::Percentage(19))
84 .bottom(Constraint::Length(3)),
85 ok_text: "Ok",
86 cancel_text: "Cancel",
87 }
88 }
89
90 pub fn styles(mut self, styles: BaseDialogStyle) -> Self {
91 self.style = styles.style;
92 if let Some(block) = styles.block {
93 self.block = block;
94 }
95 if let Some(button_style) = styles.button_style {
96 self.button_style = button_style;
97 }
98 if let Some(layout) = styles.layout {
99 self.layout = layout;
100 }
101 if let Some(ok_text) = styles.ok_text {
102 self.ok_text = ok_text;
103 }
104 if let Some(cancel_text) = styles.cancel_text {
105 self.cancel_text = cancel_text;
106 }
107 self
108 }
109
110 pub fn style(mut self, style: Style) -> Self {
112 self.style = style;
113 self
114 }
115
116 pub fn block(mut self, block: Block<'a>) -> Self {
118 self.block = block;
119 self
120 }
121
122 pub fn button_style(mut self, style: ButtonStyle) -> Self {
124 self.button_style = style;
125 self
126 }
127
128 pub fn left(mut self, left: Constraint) -> Self {
130 self.layout = self.layout.left(left);
131 self
132 }
133
134 pub fn top(mut self, top: Constraint) -> Self {
136 self.layout = self.layout.top(top);
137 self
138 }
139
140 pub fn right(mut self, right: Constraint) -> Self {
142 self.layout = self.layout.right(right);
143 self
144 }
145
146 pub fn bottom(mut self, bottom: Constraint) -> Self {
148 self.layout = self.layout.bottom(bottom);
149 self
150 }
151
152 pub fn position(mut self, pos: Position) -> Self {
154 self.layout = self.layout.position(pos);
155 self
156 }
157
158 pub fn width(mut self, width: Constraint) -> Self {
160 self.layout = self.layout.width(width);
161 self
162 }
163
164 pub fn height(mut self, height: Constraint) -> Self {
166 self.layout = self.layout.height(height);
167 self
168 }
169
170 pub fn size(mut self, size: Size) -> Self {
172 self.layout = self.layout.size(size);
173 self
174 }
175}
176
177impl<'a> StatefulWidget for BaseDialog<'a> {
178 type State = BaseDialogState;
179
180 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
181 state.area = self.layout.layout(area);
182
183 let l_dlg = layout_dialog(
184 state.area,
185 block_padding2(&self.block),
186 [Constraint::Length(12), Constraint::Length(10)],
187 1,
188 Flex::End,
189 );
190 state.widget_area = l_dlg.widget_for(DialogItem::Content);
191
192 fill_buf_area(buf, l_dlg.area(), " ", self.style);
193 self.block.render(state.area, buf);
194
195 Button::new(self.cancel_text)
196 .styles(self.button_style.clone())
197 .render(
198 l_dlg.widget_for(DialogItem::Button(0)),
199 buf,
200 &mut state.cancel,
201 );
202 Button::new(self.ok_text).styles(self.button_style).render(
203 l_dlg.widget_for(DialogItem::Button(1)),
204 buf,
205 &mut state.ok,
206 );
207 }
208}
209
210impl Default for BaseDialogState {
211 fn default() -> Self {
212 Self {
213 area: Default::default(),
214 widget_area: Default::default(),
215 ok: Default::default(),
216 cancel: Default::default(),
217 }
218 }
219}
220
221impl HasFocus for BaseDialogState {
222 fn build(&self, builder: &mut FocusBuilder) {
223 builder.widget(&self.ok);
224 builder.widget(&self.cancel);
225 }
226
227 fn focus(&self) -> FocusFlag {
228 unimplemented!()
229 }
230
231 fn area(&self) -> Rect {
232 unimplemented!()
233 }
234}
235
236impl BaseDialogState {
237 pub fn new() -> Self {
238 Self::default()
239 }
240}
241
242pub enum DialogOutcome {
244 Continue,
247 Unchanged,
250 Changed,
253 Ok,
255 Cancel,
257}
258
259impl ConsumedEvent for DialogOutcome {
260 fn is_consumed(&self) -> bool {
261 !matches!(self, DialogOutcome::Continue)
262 }
263}
264
265impl From<DialogOutcome> for Outcome {
266 fn from(value: DialogOutcome) -> Self {
267 match value {
268 DialogOutcome::Continue => Outcome::Continue,
269 DialogOutcome::Unchanged => Outcome::Unchanged,
270 DialogOutcome::Changed => Outcome::Changed,
271 DialogOutcome::Ok => Outcome::Changed,
272 DialogOutcome::Cancel => Outcome::Changed,
273 }
274 }
275}
276
277impl From<Outcome> for DialogOutcome {
278 fn from(value: Outcome) -> Self {
279 match value {
280 Outcome::Continue => DialogOutcome::Continue,
281 Outcome::Unchanged => DialogOutcome::Unchanged,
282 Outcome::Changed => DialogOutcome::Changed,
283 }
284 }
285}
286
287impl<'a> HandleEvent<Event, Dialog, DialogOutcome> for BaseDialogState {
288 fn handle(&mut self, event: &Event, _: Dialog) -> DialogOutcome {
289 flow!(match self.cancel.handle(event, Regular) {
290 ButtonOutcome::Pressed => {
291 DialogOutcome::Cancel
292 }
293 r => Outcome::from(r).into(),
294 });
295 flow!(match self.ok.handle(event, Regular) {
296 ButtonOutcome::Pressed => {
297 DialogOutcome::Ok
298 }
299 r => Outcome::from(r).into(),
300 });
301
302 flow!(match event {
303 ct_event!(keycode press Esc) => {
304 DialogOutcome::Cancel
305 }
306 ct_event!(keycode press Enter) | ct_event!(keycode press F(12)) => {
307 DialogOutcome::Ok
308 }
309 _ => DialogOutcome::Unchanged,
310 });
311
312 DialogOutcome::Unchanged
313 }
314}