cosmic/dialog/file_chooser/mod.rs
1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Dialogs for opening and save files.
5//!
6//! # Features
7//!
8//! - On Linux, the `xdg-portal` feature will use XDG Portal dialogs.
9//! - Alternatively, `rfd` can be used for platform support beyond Linux.
10//!
11//! # Open a file
12//!
13//! ```no_run
14//! cosmic::task::future(async {
15//! use cosmic::dialog::file_chooser;
16//!
17//! let dialog = file_chooser::open::Dialog::new()
18//! .title("Choose a file");
19//!
20//! match dialog.open_file().await {
21//! Ok(response) => println!("selected to open {:?}", response.url()),
22//!
23//! Err(file_chooser::Error::Cancelled) => (),
24//!
25//! Err(why) => eprintln!("error selecting file to open: {why:?}")
26//! }
27//! });
28//! ```
29//!
30//! # Open multiple files
31//!
32//! ```no_run
33//! cosmic::task::future(async {
34//! use cosmic::dialog::file_chooser;
35//!
36//! let dialog = file_chooser::open::Dialog::new()
37//! .title("Choose multiple files");
38//!
39//! match dialog.open_files().await {
40//! Ok(response) => println!("selected to open {:?}", response.urls()),
41//!
42//! Err(file_chooser::Error::Cancelled) => (),
43//!
44//! Err(why) => eprintln!("error selecting file(s) to open: {why:?}")
45//! }
46//! });
47//! ```
48//!
49//! # Open a folder
50//!
51//! ```no_run
52//! cosmic::task::future(async {
53//! use cosmic::dialog::file_chooser;
54//!
55//! let dialog = file_chooser::open::Dialog::new()
56//! .title("Choose a folder");
57//!
58//! match dialog.open_folder().await {
59//! Ok(response) => println!("selected to open {:?}", response.url()),
60//!
61//! Err(file_chooser::Error::Cancelled) => (),
62//!
63//! Err(why) => eprintln!("error selecting folder to open: {why:?}")
64//! }
65//! });
66//! ```
67//!
68//! # Open multiple folders
69//!
70//! ```no_run
71//! cosmic::task::future(async {
72//! use cosmic::dialog::file_chooser;
73//!
74//! let dialog = file_chooser::open::Dialog::new()
75//! .title("Choose a folder");
76//!
77//! match dialog.open_folders().await {
78//! Ok(response) => println!("selected to open {:?}", response.urls()),
79//!
80//! Err(file_chooser::Error::Cancelled) => (),
81//!
82//! Err(why) => eprintln!("error selecting folder(s) to open: {why:?}")
83//! }
84//! });
85//! ```
86
87/// Open file dialog.
88pub mod open;
89
90/// Save file dialog.
91pub mod save;
92
93#[cfg(xdg_portal)]
94pub use ashpd::desktop::file_chooser::{Choice, FileFilter};
95
96use thiserror::Error;
97
98#[cfg(xdg_portal)]
99fn parse_uri(uri: &ashpd::Uri) -> Result<url::Url, Error> {
100 url::Url::parse(uri.as_str()).map_err(|_| Error::UrlAbsolute)
101}
102
103/// A file filter, to limit the available file choices to certain extensions.
104#[cfg(feature = "rfd")]
105#[must_use]
106pub struct FileFilter {
107 description: String,
108 extensions: Vec<String>,
109}
110
111#[cfg(feature = "rfd")]
112impl FileFilter {
113 pub fn new(description: impl Into<String>) -> Self {
114 Self {
115 description: description.into(),
116 extensions: Vec::new(),
117 }
118 }
119
120 pub fn extension(mut self, extension: impl Into<String>) -> Self {
121 self.extensions.push(extension.into());
122 self
123 }
124}
125
126/// Errors that my occur when interacting with the file chooser subscription
127#[derive(Debug, Error)]
128pub enum Error {
129 #[error("dialog request cancelled")]
130 Cancelled,
131 #[error("dialog close failed")]
132 Close(#[source] DialogError),
133 #[error("open dialog failed")]
134 Open(#[source] DialogError),
135 #[error("dialog response failed")]
136 Response(#[source] DialogError),
137 #[error("save dialog failed")]
138 Save(#[source] DialogError),
139 #[error("could not set directory")]
140 SetDirectory(#[source] DialogError),
141 #[error("could not set absolute path for file name")]
142 SetAbsolutePath(#[source] DialogError),
143 #[error("path from dialog was not absolute")]
144 UrlAbsolute,
145}
146
147#[cfg(xdg_portal)]
148pub type DialogError = ashpd::Error;
149
150#[cfg(feature = "rfd")]
151#[derive(Debug, Error)]
152#[error("no file selected")]
153pub struct DialogError {}