1use std::cell::RefCell;
6use std::collections::VecDeque;
7use std::fmt;
8use std::mem;
9use std::path::PathBuf;
10use std::rc::Rc;
11use std::sync::Arc;
12
13use crate::backend::{Backend, Waker};
14use crate::paths::{NoHomeFolder, expand_home};
15
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19#[repr(u32)]
20pub enum DialogKind {
21 File,
23 Files,
25 Folder,
27 SavePath,
29}
30
31impl DialogKind {
32 #[must_use]
34 pub fn picks_many(self) -> bool {
35 self == Self::Files
36 }
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct FileDialog {
42 pub kind: DialogKind,
43 pub title: String,
45 pub types: Vec<String>,
47 pub folder: Option<PathBuf>,
49 pub name: String,
51}
52
53impl FileDialog {
54 pub fn new(
61 kind: DialogKind,
62 title: String,
63 types: &[&str],
64 folder: &str,
65 name: String,
66 ) -> Result<Self, NoHomeFolder> {
67 let folder = if folder.is_empty() {
68 None
69 } else {
70 Some(expand_home(folder)?).filter(|folder| folder.is_dir())
71 };
72 Ok(Self {
73 kind,
74 title,
75 types: types
76 .iter()
77 .map(|extension| extension.trim_start_matches('.'))
78 .filter(|extension| !extension.is_empty())
79 .map(str::to_owned)
80 .collect(),
81 folder,
82 name,
83 })
84 }
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
89pub enum Picked {
90 Chosen(Vec<String>),
92 Cancelled,
93 Failed(String),
95}
96
97pub type DialogHandler = Box<dyn FnOnce(Picked)>;
98
99pub trait ShowDialog {
102 fn show_dialog(&self, dialog: &FileDialog, on_close: DialogHandler);
103}
104
105impl<B: Backend> ShowDialog for B {
106 fn show_dialog(&self, dialog: &FileDialog, on_close: DialogHandler) {
107 self.show_file_dialog(dialog, on_close);
108 }
109}
110
111pub struct Dialogs<T> {
114 shower: Rc<dyn ShowDialog>,
115 waker: Arc<dyn Waker>,
116 asked: RefCell<Vec<(FileDialog, T)>>,
117 answers: Rc<RefCell<VecDeque<(T, Picked)>>>,
118}
119
120impl<T: 'static> Dialogs<T> {
121 pub fn new(shower: Rc<dyn ShowDialog>, waker: Arc<dyn Waker>) -> Self {
124 Self {
125 shower,
126 waker,
127 asked: RefCell::new(Vec::new()),
128 answers: Rc::new(RefCell::new(VecDeque::new())),
129 }
130 }
131
132 pub fn ask(&self, dialog: FileDialog, target: T) {
134 self.asked.borrow_mut().push((dialog, target));
135 self.waker.wake();
136 }
137
138 pub fn show_asked(&self) {
141 let asked = mem::take(&mut *self.asked.borrow_mut());
142 for (dialog, target) in asked {
143 let answers = Rc::clone(&self.answers);
144 let waker = Arc::clone(&self.waker);
145 self.shower.show_dialog(
146 &dialog,
147 Box::new(move |picked| {
148 answers.borrow_mut().push_back((target, picked));
149 waker.wake();
150 }),
151 );
152 }
153 }
154
155 pub fn receive<E>(&self, mut apply: impl FnMut(&T, Picked) -> Result<(), E>) -> Result<(), E> {
161 loop {
162 let next = self.answers.borrow_mut().pop_front();
163 let Some((target, picked)) = next else {
164 return Ok(());
165 };
166 apply(&target, picked)?;
167 }
168 }
169}
170
171impl<T> fmt::Debug for Dialogs<T> {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 f.debug_struct("Dialogs")
174 .field("asked", &self.asked.borrow().len())
175 .field("answers", &self.answers.borrow().len())
176 .finish_non_exhaustive()
177 }
178}
179
180#[cfg(test)]
181mod tests;