Skip to main content

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