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, Default, 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 HasFocus for BaseDialogState {
211 fn build(&self, builder: &mut FocusBuilder) {
212 builder.widget(&self.ok);
213 builder.widget(&self.cancel);
214 }
215
216 fn focus(&self) -> FocusFlag {
217 unimplemented!()
218 }
219
220 fn area(&self) -> Rect {
221 unimplemented!()
222 }
223}
224
225impl BaseDialogState {
226 pub fn new() -> Self {
227 Self::default()
228 }
229}
230
231pub enum DialogOutcome {
233 Continue,
236 Unchanged,
239 Changed,
242 Ok,
244 Cancel,
246}
247
248impl ConsumedEvent for DialogOutcome {
249 fn is_consumed(&self) -> bool {
250 !matches!(self, DialogOutcome::Continue)
251 }
252}
253
254impl From<DialogOutcome> for Outcome {
255 fn from(value: DialogOutcome) -> Self {
256 match value {
257 DialogOutcome::Continue => Outcome::Continue,
258 DialogOutcome::Unchanged => Outcome::Unchanged,
259 DialogOutcome::Changed => Outcome::Changed,
260 DialogOutcome::Ok => Outcome::Changed,
261 DialogOutcome::Cancel => Outcome::Changed,
262 }
263 }
264}
265
266impl From<Outcome> for DialogOutcome {
267 fn from(value: Outcome) -> Self {
268 match value {
269 Outcome::Continue => DialogOutcome::Continue,
270 Outcome::Unchanged => DialogOutcome::Unchanged,
271 Outcome::Changed => DialogOutcome::Changed,
272 }
273 }
274}
275
276impl HandleEvent<Event, Dialog, DialogOutcome> for BaseDialogState {
277 fn handle(&mut self, event: &Event, _: Dialog) -> DialogOutcome {
278 flow!(match self.cancel.handle(event, Regular) {
279 ButtonOutcome::Pressed => {
280 DialogOutcome::Cancel
281 }
282 r => Outcome::from(r).into(),
283 });
284 flow!(match self.ok.handle(event, Regular) {
285 ButtonOutcome::Pressed => {
286 DialogOutcome::Ok
287 }
288 r => Outcome::from(r).into(),
289 });
290
291 flow!(match event {
292 ct_event!(keycode press Esc) => {
293 DialogOutcome::Cancel
294 }
295 ct_event!(keycode press Enter) | ct_event!(keycode press F(12)) => {
296 DialogOutcome::Ok
297 }
298 _ => DialogOutcome::Unchanged,
299 });
300
301 DialogOutcome::Unchanged
302 }
303}