egui_file_dialog/config/
mod.rs

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