egui_file_dialog/config/mod.rs
1mod labels;
2pub use labels::FileDialogLabels;
3
4mod keybindings;
5use std::fmt::Display;
6use std::path::{Path, PathBuf};
7use std::sync::Arc;
8
9pub use keybindings::{FileDialogKeyBindings, KeyBinding};
10
11use crate::{FileSystem, NativeFileSystem};
12
13/// Folder that the user pinned to the left sidebar.
14#[derive(Debug, Clone)]
15#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
16pub struct PinnedFolder {
17 /// Path to the folder.
18 pub path: PathBuf,
19 /// Display name of the folder shown in the left panel.
20 pub label: String,
21}
22
23impl PinnedFolder {
24 /// Creates a new `PinnedFolder` instance from a path.
25 /// The path's file name is used as the label of the pinned folder.
26 pub fn from_path(path: PathBuf) -> Self {
27 let label = path
28 .file_name()
29 .unwrap_or_default()
30 .to_string_lossy()
31 .into_owned();
32
33 Self { path, label }
34 }
35}
36
37/// Sets which directory is loaded when opening the file dialog.
38#[derive(Debug, PartialEq, Eq, Clone)]
39pub enum OpeningMode {
40 /// The configured initial directory (`FileDialog::initial_directory`) should always be opened.
41 AlwaysInitialDir,
42 /// The directory most recently visited by the user should be opened regardless of
43 /// whether anything was picked.
44 LastVisitedDir,
45 /// The last directory from which the user picked an item should be opened.
46 LastPickedDir,
47}
48
49/// Contains configuration values of a file dialog.
50///
51/// The configuration of a file dialog can be set using `FileDialog::with_config`.
52///
53/// If you only need to configure a single file dialog, you don't need to
54/// manually use a `FileDialogConfig` object. `FileDialog` provides setter methods for
55/// each of these configuration options, for example: `FileDialog::initial_directory`
56/// or `FileDialog::default_size`.
57///
58/// `FileDialogConfig` is useful when you need to configure multiple `FileDialog` objects with the
59/// same or almost the same options.
60///
61/// # Example
62///
63/// ```
64/// use egui_file_dialog::{FileDialog, FileDialogConfig};
65///
66/// let config = FileDialogConfig {
67/// initial_directory: std::path::PathBuf::from("/app/config"),
68/// fixed_pos: Some(egui::Pos2::new(40.0, 40.0)),
69/// show_left_panel: false,
70/// ..Default::default()
71/// };
72///
73/// let file_dialog_a = FileDialog::with_config(config.clone())
74/// .id("file-dialog-a");
75///
76/// let file_dialog_b = FileDialog::with_config(config.clone());
77/// ```
78#[derive(Debug, Clone)]
79pub struct FileDialogConfig {
80 // ------------------------------------------------------------------------
81 // Core:
82 /// File system browsed by the file dialog; may be native or virtual.
83 pub file_system: Arc<dyn FileSystem + Send + Sync>,
84 /// The labels that the dialog uses.
85 pub labels: FileDialogLabels,
86 /// Keybindings used by the file dialog.
87 pub keybindings: FileDialogKeyBindings,
88
89 // ------------------------------------------------------------------------
90 // General options:
91 /// Sets which directory is loaded when opening the file dialog.
92 pub opening_mode: OpeningMode,
93 /// If the file dialog should be visible as a modal window.
94 /// This means that the input outside the window is not registered.
95 pub as_modal: bool,
96 /// Color of the overlay that is displayed under the modal to prevent user interaction.
97 pub modal_overlay_color: egui::Color32,
98 /// The first directory that will be opened when the dialog opens.
99 pub initial_directory: PathBuf,
100 /// The default filename when opening the dialog in `DialogMode::SaveFile` mode.
101 pub default_file_name: String,
102 /// If the user is allowed to select an already existing file when the dialog is
103 /// in `DialogMode::SaveFile` mode.
104 pub allow_file_overwrite: bool,
105 /// If the path edit is allowed to select the path as the file to save
106 /// if it does not have an extension.
107 ///
108 /// This can lead to confusion if the user wants to open a directory with the path edit,
109 /// types it incorrectly and the dialog tries to select the incorrectly typed folder as
110 /// the file to be saved.
111 ///
112 /// This only affects the `DialogMode::SaveFile` mode.
113 pub allow_path_edit_to_save_file_without_extension: bool,
114 /// Sets the separator of the directories when displaying a path.
115 /// Currently only used when the current path is displayed in the top panel.
116 pub directory_separator: String,
117 /// If the paths in the file dialog should be canonicalized before use.
118 pub canonicalize_paths: bool,
119 /// If the directory content should be loaded via a separate thread.
120 /// This prevents the application from blocking when loading large directories
121 /// or from slow hard drives.
122 pub load_via_thread: bool,
123 /// If we should truncate the filenames in the middle
124 pub truncate_filenames: bool,
125
126 /// Maximum number of items that can be selected at once.
127 /// `None` means no limit. Only relevant when `DialogMode::PickMultiple` is used.
128 pub max_selections: Option<usize>,
129
130 /// The icon that is used to display error messages.
131 pub err_icon: String,
132 /// The icon that is used to display warning messages.
133 pub warn_icon: String,
134 /// The default icon used to display files.
135 pub default_file_icon: String,
136 /// The default icon used to display folders.
137 pub default_folder_icon: String,
138 /// The icon used to display pinned paths in the left panel.
139 pub pinned_icon: String,
140 /// The icon used to display devices in the left panel.
141 pub device_icon: String,
142 /// The icon used to display removable devices in the left panel.
143 pub removable_device_icon: String,
144 /// The icon used for the parent directory navigation button.
145 pub parent_directory_icon: String,
146 /// The icon used for the back navigation button.
147 pub back_icon: String,
148 /// The icon used for the forward navigation button.
149 pub forward_icon: String,
150 /// The icon used for the create new folder button.
151 pub new_folder_icon: String,
152 /// The icon used for the top panel menu button.
153 pub menu_icon: String,
154 /// The icon used for the search input in the top panel.
155 pub search_icon: String,
156 /// The icon used for the path edit input in the top panel.
157 pub path_edit_icon: String,
158
159 /// Optional predicate called when the user activates a directory entry
160 /// (single-click submit via the Open button or double-click).
161 /// Return `true` to navigate *into* the directory (default behaviour);
162 /// return `false` to submit the directory as the picked path instead.
163 pub open_directory_filter: Option<Filter<Path>>,
164 /// File filters presented to the user in a dropdown.
165 pub file_filters: Vec<FileFilter>,
166 /// Name of the file filter to be selected by default.
167 pub default_file_filter: Option<String>,
168 /// File extensions presented to the user in a dropdown when saving a file.
169 pub save_extensions: Vec<SaveExtension>,
170 /// Name of the file extension selected by default.
171 pub default_save_extension: Option<String>,
172 /// Sets custom icons for different files or folders.
173 /// Use `FileDialogConfig::set_file_icon` to add a new icon to this list.
174 pub file_icon_filters: Vec<IconFilter>,
175
176 /// Custom sections added to the left sidebar for quick access.
177 /// Use `FileDialogConfig::add_quick_access` to add a new section to this list.
178 pub quick_accesses: Vec<QuickAccess>,
179
180 // ------------------------------------------------------------------------
181 // Window options:
182 /// If set, the window title will be overwritten and set to the fixed value instead
183 /// of being set dynamically.
184 pub title: Option<String>,
185 /// The ID of the window.
186 pub id: Option<egui::Id>,
187 /// The default position of the window.
188 pub default_pos: Option<egui::Pos2>,
189 /// Sets the window position and prevents it from being dragged around.
190 pub fixed_pos: Option<egui::Pos2>,
191 /// The default size of the window.
192 pub default_size: egui::Vec2,
193 /// The maximum size of the window.
194 pub max_size: Option<egui::Vec2>,
195 /// The minimum size of the window.
196 pub min_size: egui::Vec2,
197 /// The anchor of the window.
198 pub anchor: Option<(egui::Align2, egui::Vec2)>,
199 /// If the window is resizable.
200 pub resizable: bool,
201 /// If the window is movable.
202 pub movable: bool,
203 /// If the title bar of the window is shown.
204 pub title_bar: bool,
205
206 // ------------------------------------------------------------------------
207 // Feature options:
208 /// If the top panel with the navigation buttons, current path display and search input
209 /// should be visible.
210 pub show_top_panel: bool,
211 /// Whether the parent folder button should be visible at the top.
212 pub show_parent_button: bool,
213 /// Whether the back button should be visible at the top.
214 pub show_back_button: bool,
215 /// Whether the forward button should be visible at the top.
216 pub show_forward_button: bool,
217 /// If the button to create a new folder should be visible at the top.
218 pub show_new_folder_button: bool,
219 /// If the current path display in the top panel should be visible.
220 pub show_current_path: bool,
221 /// If the button to text edit the current path should be visible.
222 pub show_path_edit_button: bool,
223 /// If the menu button containing the reload button and other options should be visible.
224 pub show_menu_button: bool,
225 /// If the reload button inside the top panel menu should be visible.
226 pub show_reload_button: bool,
227 /// If the working directory shortcut in the hamburger menu should be visible.
228 pub show_working_directory_button: bool,
229 /// If the select all button in the hamburger menu should be visible.
230 pub show_select_all_button: bool,
231 /// If the show hidden files and folders option inside the top panel menu should be visible.
232 pub show_hidden_option: bool,
233 /// If the show system files option inside the top panel menu should be visible.
234 pub show_system_files_option: bool,
235 /// If the search input in the top panel should be visible.
236 pub show_search: bool,
237 /// If the default "All files" filter should be visible in the UI.
238 pub show_all_files_filter: bool,
239
240 /// Set the width of the right panel, if used
241 pub right_panel_width: Option<f32>,
242
243 /// If the sidebar with the shortcut directories such as
244 /// “Home”, “Documents” etc. should be visible.
245 pub show_left_panel: bool,
246 /// If pinned folders should be listed in the left sidebar.
247 /// Disabling this will also disable the functionality to pin a folder.
248 pub show_pinned_folders: bool,
249 /// If the Places section in the left sidebar should be visible.
250 pub show_places: bool,
251 /// If the Devices section in the left sidebar should be visible.
252 pub show_devices: bool,
253 /// If the Removable Devices section in the left sidebar should be visible.
254 pub show_removable_devices: bool,
255}
256
257impl Default for FileDialogConfig {
258 fn default() -> Self {
259 Self::default_from_filesystem(Arc::new(NativeFileSystem))
260 }
261}
262
263impl FileDialogConfig {
264 /// Creates a new configuration with default values
265 pub fn default_from_filesystem(file_system: Arc<dyn FileSystem + Send + Sync>) -> Self {
266 Self {
267 labels: FileDialogLabels::default(),
268 keybindings: FileDialogKeyBindings::default(),
269
270 opening_mode: OpeningMode::LastPickedDir,
271 as_modal: true,
272 modal_overlay_color: egui::Color32::from_rgba_premultiplied(0, 0, 0, 120),
273 initial_directory: file_system.current_dir().unwrap_or_default(),
274 default_file_name: String::from("Untitled"),
275 allow_file_overwrite: true,
276 allow_path_edit_to_save_file_without_extension: false,
277 directory_separator: String::from(">"),
278 canonicalize_paths: true,
279
280 #[cfg(target_arch = "wasm32")]
281 load_via_thread: false,
282 #[cfg(not(target_arch = "wasm32"))]
283 load_via_thread: true,
284
285 truncate_filenames: true,
286
287 max_selections: None,
288
289 err_icon: String::from("⚠"),
290 warn_icon: String::from("⚠"),
291 default_file_icon: String::from("🗋"),
292 default_folder_icon: String::from("🗀"),
293 pinned_icon: String::from("📌"),
294 device_icon: String::from("🖴"),
295 removable_device_icon: String::from("💾"),
296 parent_directory_icon: String::from("⏶"),
297 back_icon: String::from("⏴"),
298 forward_icon: String::from("⏵"),
299 new_folder_icon: String::from("+"),
300 menu_icon: String::from("☰"),
301 search_icon: String::from("🔍"),
302 path_edit_icon: String::from("🖊"),
303
304 open_directory_filter: None,
305 file_filters: Vec::new(),
306 default_file_filter: None,
307 save_extensions: Vec::new(),
308 default_save_extension: None,
309 file_icon_filters: Vec::new(),
310
311 quick_accesses: Vec::new(),
312
313 title: None,
314 id: None,
315 default_pos: None,
316 fixed_pos: None,
317 default_size: egui::Vec2::new(650.0, 370.0),
318 max_size: None,
319 min_size: egui::Vec2::new(340.0, 170.0),
320 anchor: None,
321 resizable: true,
322 movable: true,
323 title_bar: true,
324
325 show_top_panel: true,
326 show_parent_button: true,
327 show_back_button: true,
328 show_forward_button: true,
329 show_new_folder_button: true,
330 show_current_path: true,
331 show_path_edit_button: true,
332 show_menu_button: true,
333 show_reload_button: true,
334 show_working_directory_button: true,
335 show_select_all_button: true,
336 show_hidden_option: true,
337 show_system_files_option: true,
338 show_search: true,
339 show_all_files_filter: true,
340
341 right_panel_width: None,
342 show_left_panel: true,
343 show_pinned_folders: true,
344 show_places: true,
345 show_devices: true,
346 show_removable_devices: true,
347
348 file_system,
349 }
350 }
351}
352
353impl FileDialogConfig {
354 /// Sets the maximum number of items that can be selected simultaneously.
355 pub fn max_selections(mut self, max: usize) -> Self {
356 self.max_selections = Some(max);
357 self
358 }
359
360 /// Adds a new file filter the user can select from a dropdown widget.
361 ///
362 /// NOTE: The name must be unique. If a filter with the same name already exists,
363 /// it will be overwritten.
364 ///
365 /// # Arguments
366 ///
367 /// * `name` - Display name of the filter
368 /// * `filter` - Sets a filter function that checks whether a given
369 /// Path matches the criteria for this filter.
370 ///
371 /// # Examples
372 ///
373 /// ```
374 /// use std::path::Path;
375 /// use egui_file_dialog::{FileDialogConfig, Filter};
376 ///
377 /// let config = FileDialogConfig::default()
378 /// .add_file_filter(
379 /// "PNG files",
380 /// Filter::new(|path: &Path| path.extension().unwrap_or_default() == "png"))
381 /// .add_file_filter(
382 /// "JPG files",
383 /// Filter::new(|path: &Path| path.extension().unwrap_or_default() == "jpg"));
384 /// ```
385 pub fn add_file_filter(mut self, name: &str, filter: Filter<Path>) -> Self {
386 let id = egui::Id::new(name);
387
388 // Replace filter if a filter with the same name already exists.
389 if let Some(item) = self.file_filters.iter_mut().find(|p| p.id == id) {
390 item.filter = filter;
391 return self;
392 }
393
394 self.file_filters.push(FileFilter {
395 id,
396 name: name.to_owned(),
397 filter,
398 });
399
400 self
401 }
402
403 /// Shortctut method to add a file filter that matches specific extensions.
404 ///
405 /// # Arguments
406 ///
407 /// * `name` - Display name of the filter
408 /// * `extensions` - The extensions of the files to be filtered
409 ///
410 /// # Examples
411 ///
412 /// ```
413 /// use egui_file_dialog::FileDialogConfig;
414 ///
415 /// FileDialogConfig::default()
416 /// .add_file_filter_extensions("Pictures", vec!["png", "jpg", "dds"])
417 /// .add_file_filter_extensions("Rust files", vec!["rs", "toml", "lock"]);
418 pub fn add_file_filter_extensions(self, name: &str, extensions: Vec<&'static str>) -> Self {
419 self.add_file_filter(
420 name,
421 Filter::new(move |p: &Path| {
422 let extension = p
423 .extension()
424 .unwrap_or_default()
425 .to_str()
426 .unwrap_or_default();
427 extensions.contains(&extension)
428 }),
429 )
430 }
431
432 /// Adds a new file extension that the user can select in a dropdown widget when
433 /// saving a file.
434 ///
435 /// NOTE: The name must be unique. If an extension with the same name already exists,
436 /// it will be overwritten.
437 ///
438 /// # Arguments
439 ///
440 /// * `name` - Display name of the save extension.
441 /// * `file_extension` - The file extension to use.
442 ///
443 /// # Examples
444 ///
445 /// ```
446 /// use std::sync::Arc;
447 /// use egui_file_dialog::FileDialogConfig;
448 ///
449 /// let config = FileDialogConfig::default()
450 /// .add_save_extension("PNG files", "png")
451 /// .add_save_extension("JPG files", "jpg");
452 /// ```
453 pub fn add_save_extension(mut self, name: &str, file_extension: &str) -> Self {
454 let id = egui::Id::new(name);
455
456 // Replace extension when an extension with the same name already exists.
457 if let Some(item) = self.save_extensions.iter_mut().find(|p| p.id == id) {
458 file_extension.clone_into(&mut item.file_extension);
459 return self;
460 }
461
462 self.save_extensions.push(SaveExtension {
463 id,
464 name: name.to_owned(),
465 file_extension: file_extension.to_owned(),
466 });
467
468 self
469 }
470
471 /// Sets a new icon for specific files or folders.
472 ///
473 /// # Arguments
474 ///
475 /// * `icon` - The icon that should be used.
476 /// * `filter` - Sets a filter function that checks whether a given
477 /// Path matches the criteria for this icon.
478 ///
479 /// # Examples
480 ///
481 /// ```
482 /// use std::path::Path;
483 /// use egui_file_dialog::{FileDialogConfig, Filter};
484 ///
485 /// let config = FileDialogConfig::default()
486 /// // .png files should use the "document with picture (U+1F5BB)" icon.
487 /// .set_file_icon("🖻", Filter::new(|path: &Path| path.extension().unwrap_or_default() == "png"))
488 /// // .git directories should use the "web-github (U+E624)" icon.
489 /// .set_file_icon("", Filter::new(|path: &Path| path.file_name().unwrap_or_default() == ".git"));
490 /// ```
491 pub fn set_file_icon(mut self, icon: &str, filter: Filter<Path>) -> Self {
492 self.file_icon_filters.push(IconFilter {
493 icon: icon.to_string(),
494 filter,
495 });
496
497 self
498 }
499
500 /// Adds a new custom quick access section to the left panel of the file dialog.
501 ///
502 /// # Examples
503 ///
504 /// ```
505 /// use egui_file_dialog::FileDialogConfig;
506 ///
507 /// FileDialogConfig::default()
508 /// .add_quick_access("My App", |s| {
509 /// s.add_path("Config", "/app/config");
510 /// s.add_path("Themes", "/app/themes");
511 /// s.add_path("Languages", "/app/languages");
512 /// });
513 /// ```
514 pub fn add_quick_access(
515 mut self,
516 heading: &str,
517 builder: impl FnOnce(&mut QuickAccess),
518 ) -> Self {
519 let mut obj = QuickAccess {
520 canonicalize_paths: self.canonicalize_paths,
521 heading: heading.to_string(),
522 paths: Vec::new(),
523 };
524 builder(&mut obj);
525 self.quick_accesses.push(obj);
526 self
527 }
528}
529
530/// Function that returns true if the specific item matches the filter.
531pub struct Filter<T: ?Sized>(pub(crate) Arc<dyn Fn(&T) -> bool + Send + Sync>);
532
533impl<T: ?Sized> Clone for Filter<T> {
534 fn clone(&self) -> Self {
535 Self(Arc::clone(&self.0))
536 }
537}
538
539impl<T: ?Sized> Filter<T> {
540 /// Creates a new filter from a closure or function.
541 pub fn new(f: impl Fn(&T) -> bool + Send + Sync + 'static) -> Self {
542 Self(Arc::new(f))
543 }
544
545 /// Returns `true` if the item matches this filter.
546 pub(crate) fn matches(&self, item: &T) -> bool {
547 (self.0)(item)
548 }
549}
550
551impl<T: ?Sized> std::fmt::Debug for Filter<T> {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 write!(f, "Filter(..)")
554 }
555}
556
557/// Defines a specific file filter that the user can select from a dropdown.
558#[derive(Clone)]
559pub struct FileFilter {
560 /// The ID of the file filter, used internally for identification.
561 pub id: egui::Id,
562 /// The display name of the file filter
563 pub name: String,
564 /// Sets a filter function that checks whether a given Path matches the criteria for this file.
565 pub filter: Filter<Path>,
566}
567
568impl std::fmt::Debug for FileFilter {
569 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570 f.debug_struct("FileFilter")
571 .field("name", &self.name)
572 .finish()
573 }
574}
575
576/// Defines a specific file extension that the user can select when saving a file.
577#[derive(Clone, Debug)]
578pub struct SaveExtension {
579 /// The ID of the file filter, used internally for identification.
580 pub id: egui::Id,
581 /// The display name of the file filter.
582 pub name: String,
583 /// The file extension to use.
584 pub file_extension: String,
585}
586
587impl Display for SaveExtension {
588 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589 f.write_str(&format!("{} (.{})", &self.name, &self.file_extension))
590 }
591}
592
593/// Sets a specific icon for directory entries.
594#[derive(Clone)]
595pub struct IconFilter {
596 /// The icon that should be used.
597 pub icon: String,
598 /// Sets a filter function that checks whether a given Path matches the criteria for this icon.
599 pub filter: Filter<Path>,
600}
601
602impl std::fmt::Debug for IconFilter {
603 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
604 f.debug_struct("IconFilter")
605 .field("icon", &self.icon)
606 .finish()
607 }
608}
609
610/// Stores the display name and the actual path of a quick access link.
611#[derive(Debug, Clone)]
612pub struct QuickAccessPath {
613 /// Name of the path that is shown inside the left panel.
614 pub display_name: String,
615 /// Absolute or relative path to the folder.
616 pub path: PathBuf,
617}
618
619/// Stores a custom quick access section of the file dialog.
620#[derive(Debug, Clone)]
621pub struct QuickAccess {
622 /// If the path's inside the quick access section should be canonicalized.
623 canonicalize_paths: bool,
624 /// Name of the quick access section displayed inside the left panel.
625 pub heading: String,
626 /// Path's contained inside the quick access section.
627 pub paths: Vec<QuickAccessPath>,
628}
629
630impl QuickAccess {
631 /// Adds a new path to the quick access.
632 ///
633 /// Since `fs::canonicalize` is used, both absolute paths and relative paths are allowed.
634 /// See `FileDialog::canonicalize_paths` for more information.
635 ///
636 /// See `FileDialogConfig::add_quick_access` for an example.
637 pub fn add_path(&mut self, display_name: &str, path: impl Into<PathBuf>) {
638 let path = path.into();
639
640 let canonicalized_path = if self.canonicalize_paths {
641 dunce::canonicalize(&path).unwrap_or(path)
642 } else {
643 path
644 };
645
646 self.paths.push(QuickAccessPath {
647 display_name: display_name.to_string(),
648 path: canonicalized_path,
649 });
650 }
651}