Skip to main content

any_dialog/
request.rs

1//! The backend-independent description of a dialog to show.
2//!
3//! A `Request` is everything a backend needs to render one dialog and
4//! nothing about how any particular helper program wants it said. New
5//! dialog kinds land here first, then gain a rendering per backend.
6
7/// How serious a [`Request::Message`] is. Each backend maps this to
8/// its own message flags or icons.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub(crate) enum Level {
11    Info,
12    Warning,
13    Error,
14}
15
16/// A named set of file extensions for a file dialog, such as
17/// "Images" over `png` and `jpg`. Extensions are bare names,
18/// without dots or wildcards; each backend decorates them into
19/// its own filter syntax. How much of the grouping survives is
20/// backend-dependent: macOS's chooser takes one flat type list,
21/// so there the groups merge into a single extension set.
22#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct FileFilter<'a> {
24    pub name: &'a str,
25    pub extensions: &'a [&'a str],
26}
27
28impl<'a> FileFilter<'a> {
29    /// Start building a filter from its display name, as an
30    /// alternative to the struct literal. Both constructions
31    /// produce the same value:
32    ///
33    /// ```
34    /// use any_dialog::FileFilter;
35    ///
36    /// let filter = FileFilter::builder("Images").extensions(&["png", "jpg"]);
37    /// assert_eq!(
38    ///     filter,
39    ///     FileFilter { name: "Images", extensions: &["png", "jpg"] }
40    /// );
41    /// ```
42    pub fn builder(name: &'a str) -> FileFilterBuilder<'a> {
43        FileFilterBuilder { name }
44    }
45}
46
47/// Builds a [`FileFilter`] from its display name, obtained from
48/// [`FileFilter::builder`]. The only way out is
49/// [`extensions`](FileFilterBuilder::extensions), so a builder
50/// cannot be finished into a filter that has no extensions.
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub struct FileFilterBuilder<'a> {
53    name: &'a str,
54}
55
56impl<'a> FileFilterBuilder<'a> {
57    /// Finish the filter with its extensions: bare names,
58    /// without dots or wildcards.
59    pub fn extensions(self, extensions: &'a [&'a str]) -> FileFilter<'a> {
60        FileFilter {
61            name: self.name,
62            extensions,
63        }
64    }
65}
66
67/// One request, independent of which backend will render it.
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub(crate) enum Request<'a> {
70    Text {
71        title: &'a str,
72        label: &'a str,
73        initial: Option<&'a str>,
74    },
75    Password {
76        title: &'a str,
77        label: &'a str,
78    },
79    Choice {
80        title: &'a str,
81        label: &'a str,
82        choices: &'a [&'a str],
83        current: Option<&'a str>,
84    },
85    Choices {
86        title: &'a str,
87        label: &'a str,
88        choices: &'a [&'a str],
89        current: &'a [&'a str],
90    },
91    /// A color picker. `initial` is a `#rrggbb` string when the
92    /// caller has one; backends that cannot take an initial color
93    /// ignore it. Whatever form a backend answers in, the public
94    /// API normalizes it to `#rrggbb`.
95    Color {
96        title: &'a str,
97        initial: Option<&'a str>,
98    },
99    Message {
100        title: &'a str,
101        message: &'a str,
102        level: Level,
103    },
104    /// A yes/no question. Renderings that can print an answer print
105    /// the token `yes` or `no`; renderings that answer by exit code
106    /// alone report yes as success with no output and no as a
107    /// cancel. [`crate::Dialogs::confirm`] reads both forms.
108    Confirm {
109        title: &'a str,
110        message: &'a str,
111    },
112    /// Pick a file to open. `start` is where the dialog opens: a
113    /// directory, or a file whose folder it opens on. `multiple`
114    /// allows picking several files at once; the answer then
115    /// arrives one path per line.
116    OpenFile {
117        title: &'a str,
118        start: Option<&'a std::path::Path>,
119        filters: &'a [FileFilter<'a>],
120        multiple: bool,
121    },
122    /// Pick a file to save to. `start` may name a suggested file;
123    /// its name pre-fills the dialog's name field where the
124    /// backend supports that.
125    SaveFile {
126        title: &'a str,
127        start: Option<&'a std::path::Path>,
128        filters: &'a [FileFilter<'a>],
129    },
130    /// Pick a folder. `start` is the folder the dialog opens on.
131    Folder {
132        title: &'a str,
133        start: Option<&'a std::path::Path>,
134    },
135}
136
137#[cfg(test)]
138mod tests {
139    use super::FileFilter;
140
141    #[test]
142    fn builder_matches_the_struct_literal() {
143        assert_eq!(
144            FileFilter::builder("Images").extensions(&["png", "jpg"]),
145            FileFilter {
146                name: "Images",
147                extensions: &["png", "jpg"]
148            }
149        );
150    }
151}