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(feature = "xdg-portal")]
94pub use ashpd::desktop::file_chooser::{Choice, FileFilter};
95
96use thiserror::Error;
97
98/// A file filter, to limit the available file choices to certain extensions.
99#[cfg(feature = "rfd")]
100#[must_use]
101pub struct FileFilter {
102 description: String,
103 extensions: Vec<String>,
104}
105
106#[cfg(feature = "rfd")]
107impl FileFilter {
108 pub fn new(description: impl Into<String>) -> Self {
109 Self {
110 description: description.into(),
111 extensions: Vec::new(),
112 }
113 }
114
115 pub fn extension(mut self, extension: impl Into<String>) -> Self {
116 self.extensions.push(extension.into());
117 self
118 }
119}
120
121/// Errors that my occur when interacting with the file chooser subscription
122#[derive(Debug, Error)]
123pub enum Error {
124 #[error("dialog request cancelled")]
125 Cancelled,
126 #[error("dialog close failed")]
127 Close(#[source] DialogError),
128 #[error("open dialog failed")]
129 Open(#[source] DialogError),
130 #[error("dialog response failed")]
131 Response(#[source] DialogError),
132 #[error("save dialog failed")]
133 Save(#[source] DialogError),
134 #[error("could not set directory")]
135 SetDirectory(#[source] DialogError),
136 #[error("could not set absolute path for file name")]
137 SetAbsolutePath(#[source] DialogError),
138 #[error("path from dialog was not absolute")]
139 UrlAbsolute,
140}
141
142#[cfg(feature = "xdg-portal")]
143pub type DialogError = ashpd::Error;
144
145#[cfg(feature = "rfd")]
146#[derive(Debug, Error)]
147#[error("no file selected")]
148pub struct DialogError {}