1use crate::{DialogState, DialogWidget, RenderContext};
2use rat_salsa::{AppContext, Control};
3use rat_widget::event::{Dialog, FileOutcome, HandleEvent};
4use rat_widget::file_dialog::FileDialogStyle;
5use rat_widget::layout::layout_middle;
6use rat_widget::text::HasScreenCursor;
7use ratatui::buffer::Buffer;
8use ratatui::layout::Rect;
9use ratatui::prelude::Constraint;
10use ratatui::widgets::StatefulWidget;
11use std::marker::PhantomData;
12use std::path::{Path, PathBuf};
13
14pub struct FileDialog {
15 widget: rat_widget::file_dialog::FileDialog<'static>,
16}
17
18pub struct FileDialogState<Global, Event, Error> {
19 state: rat_widget::file_dialog::FileDialogState,
20 tr: Box<dyn Fn(FileOutcome) -> Control<Event> + 'static>,
21 phantom: PhantomData<(Global, Event, Error)>,
22}
23
24impl FileDialog {
25 pub fn new() -> Self {
26 Self {
27 widget: Default::default(),
28 }
29 }
30
31 pub fn styles(mut self, styles: FileDialogStyle) -> Self {
32 self.widget = self.widget.styles(styles);
33 self
34 }
35}
36
37impl<Global, Event, Error> DialogWidget<Global, Event, Error> for FileDialog
38where
39 for<'a> &'a crossterm::event::Event: TryFrom<&'a Event>,
40 Global: 'static,
41 Event: Send + 'static,
42 Error: Send + 'static + From<std::io::Error>,
43{
44 type State = dyn DialogState<Global, Event, Error>;
45
46 fn render(
47 &self,
48 area: Rect,
49 buf: &mut Buffer,
50 state: &mut Self::State,
51 ctx: &mut RenderContext<'_, Global>,
52 ) -> Result<(), Error> {
53 let state = state
54 .downcast_mut::<FileDialogState<Global, Event, Error>>()
55 .expect("state");
56
57 let dlg_area = layout_middle(
58 area,
59 Constraint::Percentage(19),
60 Constraint::Percentage(19),
61 Constraint::Length(2),
62 Constraint::Length(2),
63 );
64
65 self.widget.clone().render(dlg_area, buf, &mut state.state);
66
67 ctx.set_screen_cursor(state.state.screen_cursor());
68
69 Ok(())
70 }
71}
72
73impl<Global, Event, Error> FileDialogState<Global, Event, Error>
74where
75 for<'a> &'a crossterm::event::Event: TryFrom<&'a Event>,
76 Global: 'static,
77 Event: Send + 'static,
78 Error: Send + From<std::io::Error> + 'static,
79{
80 pub fn new() -> Self {
81 Self {
82 state: rat_widget::file_dialog::FileDialogState::new(),
83 tr: Box::new(|f| Control::from(f)),
84 phantom: Default::default(),
85 }
86 }
87
88 pub fn open_dialog(&mut self, path: impl AsRef<Path>) -> Result<(), Error> {
89 self.state.open_dialog(path)?;
90 Ok(())
91 }
92
93 pub fn save_dialog(
94 &mut self,
95 path: impl AsRef<Path>,
96 name: impl AsRef<str>,
97 ) -> Result<(), Error> {
98 self.state.save_dialog(path, name)?;
99 Ok(())
100 }
101
102 pub fn save_dialog_ext(
103 &mut self,
104 path: impl AsRef<Path>,
105 name: impl AsRef<str>,
106 ext: impl AsRef<str>,
107 ) -> Result<(), Error> {
108 self.state.save_dialog_ext(path, name, ext)?;
109 Ok(())
110 }
111
112 pub fn map_outcome(&mut self, m: impl Fn(FileOutcome) -> Control<Event> + 'static) {
113 self.tr = Box::new(m);
114 }
115
116 pub fn directory_dialog(&mut self, path: impl AsRef<Path>) -> Result<(), Error> {
117 self.state.directory_dialog(path)?;
118 Ok(())
119 }
120
121 pub fn set_filter(&mut self, filter: impl Fn(&Path) -> bool + 'static) {
123 self.state.set_filter(filter);
124 }
125
126 pub fn use_default_roots(&mut self, roots: bool) {
128 self.state.use_default_roots(roots);
129 }
130
131 pub fn add_root(&mut self, name: impl AsRef<str>, path: impl Into<PathBuf>) {
133 self.state.add_root(name, path);
134 }
135
136 pub fn clear_roots(&mut self) {
138 self.state.clear_roots();
139 }
140
141 pub fn default_roots(&mut self, start: &Path, last: &Path) {
143 self.state.default_roots(start, last);
144 }
145}
146
147impl<Global, Event, Error> DialogState<Global, Event, Error>
148 for FileDialogState<Global, Event, Error>
149where
150 for<'a> &'a crossterm::event::Event: TryFrom<&'a Event>,
151 Global: 'static,
152 Event: Send + 'static,
153 Error: Send + 'static + From<std::io::Error>,
154{
155 fn active(&self) -> bool {
156 self.state.active()
157 }
158
159 fn event(
160 &mut self,
161 event: &Event,
162 _ctx: &mut AppContext<'_, Global, Event, Error>,
163 ) -> Result<Control<Event>, Error> {
164 let r = if let Ok(event) = event.try_into() {
165 let r = self.state.handle(event, Dialog)?.into();
166 (self.tr)(r)
167 } else {
168 Control::Continue
169 };
170
171 Ok(r)
172 }
173}