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}