From c9e333f9c21ddb46c65d89ff5ee95b9fea7ab216 Mon Sep 17 00:00:00 2001 From: Jannis Date: Sat, 3 Feb 2024 19:28:32 +0100 Subject: [PATCH 01/21] Add CHANGELOG.md (#7) --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..fd778b7e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# egui-file-dialog changelog + +## Unreleased +/ + +## 2024-02-03 - v0.1.0 + +Initial release of the file dialog. + +The following features are included in this release: +- Select a file or a directory +- Save a file (Prompt user for a destination path) +- Create a new folder +- Navigation buttons to open the parent or previous directories +- Search for items in a directory +- Shortcut for user directories (Home, Documents, ...) and system disks +- Resizable window From 0a83c9e5c8c936c9d29bdb6192c841d73e07de56 Mon Sep 17 00:00:00 2001 From: Jannis Date: Sat, 3 Feb 2024 19:45:29 +0100 Subject: [PATCH 02/21] Remove egui-file-dialog version from examples (#8) * Remove egui-file-dialog version from examples * Update CHANGELOG.md --- CHANGELOG.md | 3 ++- examples/sandbox/Cargo.toml | 2 +- examples/save_file/Cargo.toml | 2 +- examples/select_directory/Cargo.toml | 2 +- examples/select_file/Cargo.toml | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd778b7e..ec9241ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # egui-file-dialog changelog ## Unreleased -/ +### 🔧 Changes +- Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) ## 2024-02-03 - v0.1.0 diff --git a/examples/sandbox/Cargo.toml b/examples/sandbox/Cargo.toml index 9764912e..17fe3629 100644 --- a/examples/sandbox/Cargo.toml +++ b/examples/sandbox/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] eframe = { version = "0.25.0", default-features = false, features = ["glow"] } -egui-file-dialog = { version = "0.1.0", path = "../../"} +egui-file-dialog = { path = "../../"} diff --git a/examples/save_file/Cargo.toml b/examples/save_file/Cargo.toml index 85a7c9c1..1cbb1a9c 100644 --- a/examples/save_file/Cargo.toml +++ b/examples/save_file/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] eframe = { version = "0.25.0", default-features = false, features = ["glow"] } -egui-file-dialog = { version = "0.1.0", path = "../../"} +egui-file-dialog = { path = "../../"} diff --git a/examples/select_directory/Cargo.toml b/examples/select_directory/Cargo.toml index fd7097ce..85630c8a 100644 --- a/examples/select_directory/Cargo.toml +++ b/examples/select_directory/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] eframe = { version = "0.25.0", default-features = false, features = ["glow"] } -egui-file-dialog = { version = "0.1.0", path = "../../"} +egui-file-dialog = { path = "../../"} diff --git a/examples/select_file/Cargo.toml b/examples/select_file/Cargo.toml index 3ba49999..a516a7e3 100644 --- a/examples/select_file/Cargo.toml +++ b/examples/select_file/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] eframe = { version = "0.25.0", default-features = false, features = ["glow"] } -egui-file-dialog = { version = "0.1.0", path = "../../"} +egui-file-dialog = { path = "../../"} From 6ad46ed99028f7750a4956b024a4c6b184ce3e26 Mon Sep 17 00:00:00 2001 From: Jannis Date: Sat, 3 Feb 2024 19:52:51 +0100 Subject: [PATCH 03/21] Fix syntax highlighting on crates.io (#9) * Fix syntax highlighting on crates.io * Update CHANGELOG.md --- CHANGELOG.md | 3 +++ README.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec9241ee..153868f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) +### 📚 Documentation +- Fix syntax highlighting on crates.io [#9](https://github.com/fluxxcode/egui-file-dialog/pull/9) + ## 2024-02-03 - v0.1.0 Initial release of the file dialog. diff --git a/README.md b/README.md index 2ad338c9..bbf837b7 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ egui-file-dialog = "0.1.0" ``` main.rs: -```rs +```rust use std::path::PathBuf; use eframe::egui; From 1e34e5af0b29a7ee64fff97e0259f4167a736f0f Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 10:48:14 +0100 Subject: [PATCH 04/21] Add dependency badge (#10) * Add dependency badge to README.md * Update CHANGELOG.md --- CHANGELOG.md | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 153868f8..31916145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### 📚 Documentation - Fix syntax highlighting on crates.io [#9](https://github.com/fluxxcode/egui-file-dialog/pull/9) +- Added dependency badge to `README.md` [#10](https://github.com/fluxxcode/egui-file-dialog/pull/10) ## 2024-02-03 - v0.1.0 diff --git a/README.md b/README.md index bbf837b7..11e5b67c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # egui-file-dialog [![Latest version](https://img.shields.io/crates/v/egui-file-dialog.svg)](https://crates.io/crates/egui-file-dialog) [![Documentation](https://docs.rs/egui-file-dialog/badge.svg)](https://docs.rs/egui-file-dialog) +[![Dependency status](https://deps.rs/repo/github/fluxxcode/egui-file-dialog/status.svg)](https://deps.rs/repo/github/fluxxcode/egui-file-dialog) [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fluxxcode/egui-file-dialog/blob/master/LICENSE) This repository provides an easy-to-use file dialog (a.k.a. file explorer, file picker) for [egui](https://github.com/emilk/egui). This makes it possible to use a file dialog directly in the egui application without having to rely on the file explorer of the operating system. From 01a2ad8b25411c960e3a8b004da29a7a1292bfc8 Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 12:09:34 +0100 Subject: [PATCH 05/21] Add function to set window anchor (#11) * Add FileDialog::anchor * Update CHANGELOG.md --- CHANGELOG.md | 3 ++ examples/sandbox/src/main.rs | 3 +- src/file_dialog.rs | 73 +++++++++++++++++++++++------------- 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31916145..41a7c262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # egui-file-dialog changelog ## Unreleased +### ✨ Features +- Added `FileDialog::anchor` to overwrite the window anchor [#11](https://github.com/fluxxcode/egui-file-dialog/pull/11) + ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) diff --git a/examples/sandbox/src/main.rs b/examples/sandbox/src/main.rs index d0a00854..b6f2dbc8 100644 --- a/examples/sandbox/src/main.rs +++ b/examples/sandbox/src/main.rs @@ -14,7 +14,8 @@ struct MyApp { impl MyApp { pub fn new(_cc: &eframe::CreationContext) -> Self { Self { - file_dialog: FileDialog::new(), + file_dialog: FileDialog::new() + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::new(0.0, 0.0)), selected_directory: None, selected_file: None, diff --git a/src/file_dialog.rs b/src/file_dialog.rs index ee26906c..9c71d0db 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -99,6 +99,8 @@ pub struct FileDialog { window_title: String, /// The default size of the window. default_window_size: egui::Vec2, + /// The anchor of the window. + window_anchor: Option<(egui::Align2, egui::Vec2)>, /// The dialog that is shown when the user wants to create a new directory. create_directory_dialog: CreateDirectoryDialog, @@ -146,6 +148,7 @@ impl FileDialog { window_title: String::from("Select directory"), default_window_size: egui::Vec2::new(650.0, 370.0), + window_anchor: None, create_directory_dialog: CreateDirectoryDialog::new(), @@ -177,6 +180,12 @@ impl FileDialog { self } + /// Sets the anchor of the window. + pub fn anchor(mut self, align: egui::Align2, offset: egui::Vec2) -> Self { + self.window_anchor = Some((align, offset)); + self + } + /// Sets the default file name when opening the dialog in `DialogMode::SaveFile` mode. pub fn default_file_name(mut self, name: &str) -> Self { self.default_file_name = name.to_string(); @@ -284,37 +293,31 @@ impl FileDialog { let mut is_open = true; - egui::Window::new(&self.window_title) - .open(&mut is_open) - .default_size(self.default_window_size) - .min_width(335.0) - .min_height(200.0) - .collapsible(false) - .show(ctx, |ui| { - egui::TopBottomPanel::top("fe_top_panel") - .resizable(false) - .show_inside(ui, |ui| { - self.ui_update_top_panel(ctx, ui); - }); - - egui::SidePanel::left("fe_left_panel") - .resizable(true) - .default_width(150.0) - .width_range(90.0..=250.0) - .show_inside(ui, |ui| { - self.ui_update_left_panel(ctx, ui); - }); + self.create_window(&mut is_open).show(ctx, |ui| { + egui::TopBottomPanel::top("fe_top_panel") + .resizable(false) + .show_inside(ui, |ui| { + self.ui_update_top_panel(ctx, ui); + }); - egui::TopBottomPanel::bottom("fe_bottom_panel") - .resizable(false) - .show_inside(ui, |ui| { - self.ui_update_bottom_panel(ctx, ui); - }); + egui::SidePanel::left("fe_left_panel") + .resizable(true) + .default_width(150.0) + .width_range(90.0..=250.0) + .show_inside(ui, |ui| { + self.ui_update_left_panel(ctx, ui); + }); - egui::CentralPanel::default().show_inside(ui, |ui| { - self.ui_update_central_panel(ui); + egui::TopBottomPanel::bottom("fe_bottom_panel") + .resizable(false) + .show_inside(ui, |ui| { + self.ui_update_bottom_panel(ctx, ui); }); + + egui::CentralPanel::default().show_inside(ui, |ui| { + self.ui_update_central_panel(ui); }); + }); // User closed the window without finishing the dialog if !is_open { @@ -324,6 +327,22 @@ impl FileDialog { self } + /// Creates a new egui window with the configured options. + fn create_window<'a>(&self, is_open: &'a mut bool) -> egui::Window<'a> { + let mut window = egui::Window::new(&self.window_title) + .open(is_open) + .default_size(self.default_window_size) + .min_width(355.0) + .min_height(200.0) + .collapsible(false); + + if let Some((anchor, offset)) = self.window_anchor { + window = window.anchor(anchor, offset); + } + + window + } + /// Updates the top panel of the dialog. Including the navigation buttons, /// the current path display, the reload button and the search field. fn ui_update_top_panel(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) { From 164ec7e0408278aa72ea95567dd8c00a99f55fbd Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 18:01:14 +0100 Subject: [PATCH 06/21] Add function to overwrite window title (#12) * Add function to overwrite the window title * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/file_dialog.rs | 27 ++++++++++++++++++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41a7c262..99f3f282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased ### ✨ Features - Added `FileDialog::anchor` to overwrite the window anchor [#11](https://github.com/fluxxcode/egui-file-dialog/pull/11) +- Added `FileDialog::title` to overwrite the window title [#12](https://github.com/fluxxcode/egui-file-dialog/pull/12) ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 9c71d0db..172773c2 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -97,6 +97,9 @@ pub struct FileDialog { /// The currently used window title. /// This changes depending on the mode the dialog is in. window_title: String, + /// If set, the window title will be overwritten and set to the fixed value instead + /// of being set dynamically. + overwrite_window_title: Option, /// The default size of the window. default_window_size: egui::Vec2, /// The anchor of the window. @@ -147,6 +150,7 @@ impl FileDialog { directory_error: None, window_title: String::from("Select directory"), + overwrite_window_title: None, default_window_size: egui::Vec2::new(650.0, 370.0), window_anchor: None, @@ -174,6 +178,15 @@ impl FileDialog { self } + /// Overwrites the window title. + /// + /// By default, the title is set dynamically, based on the `DialogMode` + /// the dialog is currently in. + pub fn title(mut self, title: &str) -> Self { + self.overwrite_window_title = Some(title.to_string()); + self + } + /// Sets the default size of the window. pub fn default_window_size(mut self, size: egui::Vec2) -> Self { self.default_window_size = size; @@ -224,11 +237,15 @@ impl FileDialog { self.state = DialogState::Open; self.show_files = show_files; - self.window_title = match mode { - DialogMode::SelectDirectory => "📁 Select Folder".to_string(), - DialogMode::SelectFile => "📂 Open File".to_string(), - DialogMode::SaveFile => "📥 Save File".to_string(), - }; + if let Some(title) = &self.overwrite_window_title { + self.window_title = title.clone(); + } else { + self.window_title = match mode { + DialogMode::SelectDirectory => "📁 Select Folder".to_string(), + DialogMode::SelectFile => "📂 Open File".to_string(), + DialogMode::SaveFile => "📥 Save File".to_string(), + }; + } self.load_directory(&self.initial_directory.clone()) } From c49207795814bf895b3ff5a5d204aa9efc8a1205 Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 18:35:34 +0100 Subject: [PATCH 07/21] Improve API (#13) Improved API by adding `impl Into` to window functions --- src/file_dialog.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 172773c2..49820f23 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -188,14 +188,14 @@ impl FileDialog { } /// Sets the default size of the window. - pub fn default_window_size(mut self, size: egui::Vec2) -> Self { - self.default_window_size = size; + pub fn default_window_size(mut self, size: impl Into) -> Self { + self.default_window_size = size.into(); self } /// Sets the anchor of the window. - pub fn anchor(mut self, align: egui::Align2, offset: egui::Vec2) -> Self { - self.window_anchor = Some((align, offset)); + pub fn anchor(mut self, align: egui::Align2, offset: impl Into) -> Self { + self.window_anchor = Some((align, offset.into())); self } From 2c8272938490157e33a3f3ded645ff9beb6cbeeb Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 18:55:46 +0100 Subject: [PATCH 08/21] Rename window variables (#14) * Rename window vars * Update CHANGELOG.md --- CHANGELOG.md | 3 +++ src/file_dialog.rs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99f3f282..e3e3ff59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # egui-file-dialog changelog ## Unreleased +### 🚨 Breaking Changes +- Rename `FileDialog::default_window_size` to `FileDialog::default_size` [#14](https://github.com/fluxxcode/egui-file-dialog/pull/14) + ### ✨ Features - Added `FileDialog::anchor` to overwrite the window anchor [#11](https://github.com/fluxxcode/egui-file-dialog/pull/11) - Added `FileDialog::title` to overwrite the window title [#12](https://github.com/fluxxcode/egui-file-dialog/pull/12) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 49820f23..ca40328f 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -99,9 +99,9 @@ pub struct FileDialog { window_title: String, /// If set, the window title will be overwritten and set to the fixed value instead /// of being set dynamically. - overwrite_window_title: Option, + window_overwrite_title: Option, /// The default size of the window. - default_window_size: egui::Vec2, + window_default_size: egui::Vec2, /// The anchor of the window. window_anchor: Option<(egui::Align2, egui::Vec2)>, @@ -150,8 +150,8 @@ impl FileDialog { directory_error: None, window_title: String::from("Select directory"), - overwrite_window_title: None, - default_window_size: egui::Vec2::new(650.0, 370.0), + window_overwrite_title: None, + window_default_size: egui::Vec2::new(650.0, 370.0), window_anchor: None, create_directory_dialog: CreateDirectoryDialog::new(), @@ -183,13 +183,13 @@ impl FileDialog { /// By default, the title is set dynamically, based on the `DialogMode` /// the dialog is currently in. pub fn title(mut self, title: &str) -> Self { - self.overwrite_window_title = Some(title.to_string()); + self.window_overwrite_title = Some(title.to_string()); self } /// Sets the default size of the window. - pub fn default_window_size(mut self, size: impl Into) -> Self { - self.default_window_size = size.into(); + pub fn default_size(mut self, size: impl Into) -> Self { + self.window_default_size = size.into(); self } @@ -237,7 +237,7 @@ impl FileDialog { self.state = DialogState::Open; self.show_files = show_files; - if let Some(title) = &self.overwrite_window_title { + if let Some(title) = &self.window_overwrite_title { self.window_title = title.clone(); } else { self.window_title = match mode { @@ -348,7 +348,7 @@ impl FileDialog { fn create_window<'a>(&self, is_open: &'a mut bool) -> egui::Window<'a> { let mut window = egui::Window::new(&self.window_title) .open(is_open) - .default_size(self.default_window_size) + .default_size(self.window_default_size) .min_width(355.0) .min_height(200.0) .collapsible(false); From f8548cbb08d9462a5285d80e12ad9be15c0a9eac Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 19:10:23 +0100 Subject: [PATCH 09/21] Resizable and movable option (#15) * Add FileDialog::resizable * Add FileDialog::movable * Update CHANGELOG.md --- CHANGELOG.md | 2 ++ src/file_dialog.rs | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3e3ff59..8ed14740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ ### ✨ Features - Added `FileDialog::anchor` to overwrite the window anchor [#11](https://github.com/fluxxcode/egui-file-dialog/pull/11) - Added `FileDialog::title` to overwrite the window title [#12](https://github.com/fluxxcode/egui-file-dialog/pull/12) +- Added `FileDialog::resizable` to set if the window is resizable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) +- Added `FileDialog::movable` to set if the window is movable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index ca40328f..210fb8a3 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -104,6 +104,10 @@ pub struct FileDialog { window_default_size: egui::Vec2, /// The anchor of the window. window_anchor: Option<(egui::Align2, egui::Vec2)>, + /// If the window is resizable + window_resizable: bool, + /// If the window is movable + window_movable: bool, /// The dialog that is shown when the user wants to create a new directory. create_directory_dialog: CreateDirectoryDialog, @@ -153,6 +157,8 @@ impl FileDialog { window_overwrite_title: None, window_default_size: egui::Vec2::new(650.0, 370.0), window_anchor: None, + window_resizable: true, + window_movable: true, create_directory_dialog: CreateDirectoryDialog::new(), @@ -199,6 +205,20 @@ impl FileDialog { self } + /// Sets if the window is resizable. + pub fn resizable(mut self, resizable: bool) -> Self { + self.window_resizable = resizable; + self + } + + /// Sets if the window is movable. + /// + /// Has no effect if an anchor is set. + pub fn movable(mut self, movable: bool) -> Self { + self.window_movable = movable; + self + } + /// Sets the default file name when opening the dialog in `DialogMode::SaveFile` mode. pub fn default_file_name(mut self, name: &str) -> Self { self.default_file_name = name.to_string(); @@ -351,6 +371,8 @@ impl FileDialog { .default_size(self.window_default_size) .min_width(355.0) .min_height(200.0) + .resizable(self.window_resizable) + .movable(self.window_movable) .collapsible(false); if let Some((anchor, offset)) = self.window_anchor { From 598885865aa0051e2b99982791b50da51bd74d4e Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 19:43:43 +0100 Subject: [PATCH 10/21] Add function to set the ID of the window (#16) * Add function to set the window ID * Update CHANGELOG.md --- CHANGELOG.md | 1 + examples/sandbox/src/main.rs | 3 ++- src/file_dialog.rs | 13 +++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed14740..bf162748 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - Added `FileDialog::title` to overwrite the window title [#12](https://github.com/fluxxcode/egui-file-dialog/pull/12) - Added `FileDialog::resizable` to set if the window is resizable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) - Added `FileDialog::movable` to set if the window is movable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) +- Added `FileDialog::id` to set the ID of the window [#16](https://github.com/fluxxcode/egui-file-dialog/pull/16) ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) diff --git a/examples/sandbox/src/main.rs b/examples/sandbox/src/main.rs index b6f2dbc8..c9d74765 100644 --- a/examples/sandbox/src/main.rs +++ b/examples/sandbox/src/main.rs @@ -15,7 +15,8 @@ impl MyApp { pub fn new(_cc: &eframe::CreationContext) -> Self { Self { file_dialog: FileDialog::new() - .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::new(0.0, 0.0)), + .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::new(0.0, 0.0)) + .id("egui_file_dialog"), selected_directory: None, selected_file: None, diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 210fb8a3..ee2c6dc7 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -100,6 +100,8 @@ pub struct FileDialog { /// If set, the window title will be overwritten and set to the fixed value instead /// of being set dynamically. window_overwrite_title: Option, + /// The ID of the window. + window_id: Option, /// The default size of the window. window_default_size: egui::Vec2, /// The anchor of the window. @@ -155,6 +157,7 @@ impl FileDialog { window_title: String::from("Select directory"), window_overwrite_title: None, + window_id: None, window_default_size: egui::Vec2::new(650.0, 370.0), window_anchor: None, window_resizable: true, @@ -193,6 +196,12 @@ impl FileDialog { self } + /// Sets the ID of the window. + pub fn id(mut self, id: impl Into) -> Self { + self.window_id = Some(id.into()); + self + } + /// Sets the default size of the window. pub fn default_size(mut self, size: impl Into) -> Self { self.window_default_size = size.into(); @@ -375,6 +384,10 @@ impl FileDialog { .movable(self.window_movable) .collapsible(false); + if let Some(id) = self.window_id { + window = window.id(id); + } + if let Some((anchor, offset)) = self.window_anchor { window = window.anchor(anchor, offset); } From 5adcf9216bac55b2ab6c5a8d0efbe4b24a8fcfcc Mon Sep 17 00:00:00 2001 From: Jannis Date: Sun, 4 Feb 2024 20:12:48 +0100 Subject: [PATCH 11/21] Add functions to set window position (#17) * Add functions to set window position * Update CHANGELOG.md --- CHANGELOG.md | 1 + examples/sandbox/src/main.rs | 4 +--- src/file_dialog.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf162748..16dab5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Added `FileDialog::resizable` to set if the window is resizable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) - Added `FileDialog::movable` to set if the window is movable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) - Added `FileDialog::id` to set the ID of the window [#16](https://github.com/fluxxcode/egui-file-dialog/pull/16) +- Added `FileDialog::fixed_pos` and `FileDialog::default_pos` to set the position of the window [#17](https://github.com/fluxxcode/egui-file-dialog/pull/17) ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) diff --git a/examples/sandbox/src/main.rs b/examples/sandbox/src/main.rs index c9d74765..53e7d961 100644 --- a/examples/sandbox/src/main.rs +++ b/examples/sandbox/src/main.rs @@ -14,9 +14,7 @@ struct MyApp { impl MyApp { pub fn new(_cc: &eframe::CreationContext) -> Self { Self { - file_dialog: FileDialog::new() - .anchor(egui::Align2::CENTER_CENTER, egui::Vec2::new(0.0, 0.0)) - .id("egui_file_dialog"), + file_dialog: FileDialog::new().id("egui_file_dialog"), selected_directory: None, selected_file: None, diff --git a/src/file_dialog.rs b/src/file_dialog.rs index ee2c6dc7..d5c18442 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -102,6 +102,10 @@ pub struct FileDialog { window_overwrite_title: Option, /// The ID of the window. window_id: Option, + /// The default position of the window. + window_default_pos: Option, + /// Sets the window position and prevents it from being dragged around. + window_fixed_pos: Option, /// The default size of the window. window_default_size: egui::Vec2, /// The anchor of the window. @@ -158,6 +162,8 @@ impl FileDialog { window_title: String::from("Select directory"), window_overwrite_title: None, window_id: None, + window_default_pos: None, + window_fixed_pos: None, window_default_size: egui::Vec2::new(650.0, 370.0), window_anchor: None, window_resizable: true, @@ -202,6 +208,18 @@ impl FileDialog { self } + /// Sets the default position of the window. + pub fn default_pos(mut self, default_pos: impl Into) -> Self { + self.window_default_pos = Some(default_pos.into()); + self + } + + /// Sets the window position and prevents it from being dragged around. + pub fn fixed_pos(mut self, pos: impl Into) -> Self { + self.window_fixed_pos = Some(pos.into()); + self + } + /// Sets the default size of the window. pub fn default_size(mut self, size: impl Into) -> Self { self.window_default_size = size.into(); @@ -388,6 +406,14 @@ impl FileDialog { window = window.id(id); } + if let Some(pos) = self.window_default_pos { + window = window.default_pos(pos); + } + + if let Some(pos) = self.window_fixed_pos { + window = window.fixed_pos(pos); + } + if let Some((anchor, offset)) = self.window_anchor { window = window.anchor(anchor, offset); } From 4e3b77aa41652de923bf3bab3c7b91953d773b2b Mon Sep 17 00:00:00 2001 From: Jannis Date: Mon, 5 Feb 2024 21:37:50 +0100 Subject: [PATCH 12/21] Fix an issue where no error message appears when creating a folder (#18) * Update to scroll to the error when creating a dir * Update CHANGELOG.md * Fix linter error --- CHANGELOG.md | 3 +++ src/create_directory_dialog.rs | 30 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16dab5dc..39556475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ - Added `FileDialog::id` to set the ID of the window [#16](https://github.com/fluxxcode/egui-file-dialog/pull/16) - Added `FileDialog::fixed_pos` and `FileDialog::default_pos` to set the position of the window [#17](https://github.com/fluxxcode/egui-file-dialog/pull/17) +### 🐛 Bug Fixes +- Fixed issue where no error message was displayed when creating a folder [#18](https://github.com/fluxxcode/egui-file-dialog/pull/18) + ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) diff --git a/src/create_directory_dialog.rs b/src/create_directory_dialog.rs index 589e3aa7..6036b178 100644 --- a/src/create_directory_dialog.rs +++ b/src/create_directory_dialog.rs @@ -42,6 +42,8 @@ pub struct CreateDirectoryDialog { input: String, /// This contains the error message if the folder name is invalid error: Option, + /// If we should scroll to the error in the next frame + scroll_to_error: bool, } impl CreateDirectoryDialog { @@ -54,6 +56,7 @@ impl CreateDirectoryDialog { input: String::new(), error: None, + scroll_to_error: false, } } @@ -108,7 +111,12 @@ impl CreateDirectoryDialog { if let Some(err) = &self.error { ui.add_space(5.0); - ui.label(err); + let response = ui.label(err); + + if self.scroll_to_error { + response.scroll_to_me(Some(egui::Align::Center)); + self.scroll_to_error = false; + } } result @@ -132,7 +140,7 @@ impl CreateDirectoryDialog { return CreateDirectoryResponse::new(dir.as_path()); } Err(err) => { - self.error = Some(format!("Error: {}", err)); + self.error = self.create_error(format!("Error: {}", err).as_str()); return CreateDirectoryResponse::new_empty(); } } @@ -141,7 +149,7 @@ impl CreateDirectoryDialog { // This error should not occur because the create_directory function is only // called when the dialog is open and the directory is set. // If this error occurs, there is most likely a bug in the code. - self.error = Some("No directory given".to_string()); + self.error = self.create_error("No directory given"); CreateDirectoryResponse::new_empty() } @@ -150,28 +158,34 @@ impl CreateDirectoryDialog { /// Returns None if the name is valid. Otherwise returns the error message. fn validate_input(&mut self) -> Option { if self.input.is_empty() { - return Some("Name of the folder can not be empty".to_string()); + return self.create_error("Name of the folder can not be empty"); } if let Some(mut x) = self.directory.clone() { x.push(self.input.as_str()); if x.is_dir() { - return Some("A directory with the name already exists".to_string()); + return self.create_error("A directory with the name already exists"); } if x.is_file() { - return Some("A file with the name already exists".to_string()); + return self.create_error("A file with the name already exists"); } } else { // This error should not occur because the validate_input function is only // called when the dialog is open and the directory is set. // If this error occurs, there is most likely a bug in the code. - return Some("No directory given".to_string()); + return self.create_error("No directory given"); } None } + /// Creates the specified error and sets to scroll to the error in the next frame. + fn create_error(&mut self, error: &str) -> Option { + self.scroll_to_error = true; + Some(error.to_string()) + } + /// Resets the dialog. /// Configuration variables are not changed. fn reset(&mut self) { @@ -179,5 +193,7 @@ impl CreateDirectoryDialog { self.init = false; self.directory = None; self.input.clear(); + self.error = None; + self.scroll_to_error = false; } } From 29274b2a1d3905e5e4f2b7d1036a555406979446 Mon Sep 17 00:00:00 2001 From: Jannis Date: Mon, 5 Feb 2024 21:45:49 +0100 Subject: [PATCH 13/21] Updated docs badge to use shields.io (#19) * Update documentation badge Updated documentation badge to use shields.io instead of docs.rs * Udpate CHANGELOG.md --- CHANGELOG.md | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39556475..fa332aa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### 📚 Documentation - Fix syntax highlighting on crates.io [#9](https://github.com/fluxxcode/egui-file-dialog/pull/9) - Added dependency badge to `README.md` [#10](https://github.com/fluxxcode/egui-file-dialog/pull/10) +- Updated docs badge to use shields.io [#19](https://github.com/fluxxcode/egui-file-dialog/pull/19) ## 2024-02-03 - v0.1.0 diff --git a/README.md b/README.md index 11e5b67c..ddcc1a96 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # egui-file-dialog [![Latest version](https://img.shields.io/crates/v/egui-file-dialog.svg)](https://crates.io/crates/egui-file-dialog) -[![Documentation](https://docs.rs/egui-file-dialog/badge.svg)](https://docs.rs/egui-file-dialog) +![Documentation](https://img.shields.io/docsrs/egui-file-dialog) [![Dependency status](https://deps.rs/repo/github/fluxxcode/egui-file-dialog/status.svg)](https://deps.rs/repo/github/fluxxcode/egui-file-dialog) [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fluxxcode/egui-file-dialog/blob/master/LICENSE) From 2f4902cbb643dc861459ed67571c8e44b86f6385 Mon Sep 17 00:00:00 2001 From: Jannis Date: Mon, 5 Feb 2024 21:51:32 +0100 Subject: [PATCH 14/21] Fix link of docs badge (#20) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ddcc1a96..6ade02a3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # egui-file-dialog [![Latest version](https://img.shields.io/crates/v/egui-file-dialog.svg)](https://crates.io/crates/egui-file-dialog) -![Documentation](https://img.shields.io/docsrs/egui-file-dialog) +[![Documentation](https://img.shields.io/docsrs/egui-file-dialog)](https://docs.rs/egui-file-dialog) [![Dependency status](https://deps.rs/repo/github/fluxxcode/egui-file-dialog/status.svg)](https://deps.rs/repo/github/fluxxcode/egui-file-dialog) [![MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/fluxxcode/egui-file-dialog/blob/master/LICENSE) From 3f795fc4c3ff37382aa6101df2895171740220aa Mon Sep 17 00:00:00 2001 From: Jannis Date: Mon, 5 Feb 2024 22:10:23 +0100 Subject: [PATCH 15/21] Add functions to set min and max window size (#21) * Add function to set min and max window size * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/file_dialog.rs | 27 +++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa332aa6..a51428f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Added `FileDialog::movable` to set if the window is movable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) - Added `FileDialog::id` to set the ID of the window [#16](https://github.com/fluxxcode/egui-file-dialog/pull/16) - Added `FileDialog::fixed_pos` and `FileDialog::default_pos` to set the position of the window [#17](https://github.com/fluxxcode/egui-file-dialog/pull/17) +- Added `FileDialog::min_size` and `FileDialog::max_size` to set the minimum and maximum size of the window [#21](https://github.com/fluxxcode/egui-file-dialog/pull/21) ### 🐛 Bug Fixes - Fixed issue where no error message was displayed when creating a folder [#18](https://github.com/fluxxcode/egui-file-dialog/pull/18) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index d5c18442..0bfdc847 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -108,6 +108,10 @@ pub struct FileDialog { window_fixed_pos: Option, /// The default size of the window. window_default_size: egui::Vec2, + /// The maximum size of the window. + window_max_size: Option, + /// The minimum size of the window. + window_min_size: egui::Vec2, /// The anchor of the window. window_anchor: Option<(egui::Align2, egui::Vec2)>, /// If the window is resizable @@ -165,6 +169,8 @@ impl FileDialog { window_default_pos: None, window_fixed_pos: None, window_default_size: egui::Vec2::new(650.0, 370.0), + window_max_size: None, + window_min_size: egui::Vec2::new(355.0, 200.0), window_anchor: None, window_resizable: true, window_movable: true, @@ -226,6 +232,20 @@ impl FileDialog { self } + /// Sets the maximum size of the window. + pub fn max_size(mut self, max_size: impl Into) -> Self { + self.window_max_size = Some(max_size.into()); + self + } + + /// Sets the minimum size of the window. + /// + /// Specifying a smaller minimum size than the default can lead to unexpected behavior. + pub fn min_size(mut self, min_size: impl Into) -> Self { + self.window_min_size = min_size.into(); + self + } + /// Sets the anchor of the window. pub fn anchor(mut self, align: egui::Align2, offset: impl Into) -> Self { self.window_anchor = Some((align, offset.into())); @@ -396,8 +416,7 @@ impl FileDialog { let mut window = egui::Window::new(&self.window_title) .open(is_open) .default_size(self.window_default_size) - .min_width(355.0) - .min_height(200.0) + .min_size(self.window_min_size) .resizable(self.window_resizable) .movable(self.window_movable) .collapsible(false); @@ -418,6 +437,10 @@ impl FileDialog { window = window.anchor(anchor, offset); } + if let Some(size) = self.window_max_size { + window = window.max_size(size); + } + window } From 5c26325bb6f9d2d8a476aaf0d47d071bbec4914e Mon Sep 17 00:00:00 2001 From: Jannis Date: Mon, 5 Feb 2024 22:37:51 +0100 Subject: [PATCH 16/21] Use ui.add_enabled instead of custom UI module (#22) * Use ui.add_enabled instead of custom ui module * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/create_directory_dialog.rs | 7 +++--- src/file_dialog.rs | 41 +++++++++++++++++++------------- src/lib.rs | 1 - src/ui.rs | 43 ---------------------------------- 5 files changed, 30 insertions(+), 63 deletions(-) delete mode 100644 src/ui.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a51428f4..6488b87a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) +- Use `ui.add_enabled` instead of custom `ui.rs` module [#22](https://github.com/fluxxcode/egui-file-dialog/pull/22) ### 📚 Documentation - Fix syntax highlighting on crates.io [#9](https://github.com/fluxxcode/egui-file-dialog/pull/9) diff --git a/src/create_directory_dialog.rs b/src/create_directory_dialog.rs index 6036b178..7027c588 100644 --- a/src/create_directory_dialog.rs +++ b/src/create_directory_dialog.rs @@ -1,8 +1,6 @@ use std::fs; use std::path::{Path, PathBuf}; -use crate::ui; - pub struct CreateDirectoryResponse { /// Contains the path to the directory that was created. directory: Option, @@ -100,7 +98,10 @@ impl CreateDirectoryDialog { self.error = self.validate_input(); } - if ui::button_enabled_disabled(ui, "✔", self.error.is_none()) { + if ui + .add_enabled(self.error.is_none(), egui::Button::new("✔")) + .clicked() + { result = self.create_directory(); } diff --git a/src/file_dialog.rs b/src/file_dialog.rs index 0bfdc847..cfb44a91 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -6,7 +6,6 @@ use std::{fs, io}; use crate::create_directory_dialog::CreateDirectoryDialog; use crate::data::{DirectoryContent, DirectoryEntry, Disks, UserDirectories}; -use crate::ui; /// Represents the mode the file dialog is currently in. #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -452,37 +451,31 @@ impl FileDialog { ui.horizontal(|ui| { // Navigation buttons if let Some(x) = self.current_directory() { - if ui::button_sized_enabled_disabled(ui, NAV_BUTTON_SIZE, "⏶", x.parent().is_some()) - { + if self.ui_button_sized(ui, x.parent().is_some(), NAV_BUTTON_SIZE, "⏶") { let _ = self.load_parent_directory(); } } else { - let _ = ui::button_sized_enabled_disabled(ui, NAV_BUTTON_SIZE, "⏶", false); + let _ = self.ui_button_sized(ui, false, NAV_BUTTON_SIZE, "⏶"); } - if ui::button_sized_enabled_disabled( + if self.ui_button_sized( ui, + self.directory_offset + 1 < self.directory_stack.len(), NAV_BUTTON_SIZE, "⏴", - self.directory_offset + 1 < self.directory_stack.len(), ) { let _ = self.load_previous_directory(); } - if ui::button_sized_enabled_disabled( - ui, - NAV_BUTTON_SIZE, - "⏵", - self.directory_offset != 0, - ) { + if self.ui_button_sized(ui, self.directory_offset != 0, NAV_BUTTON_SIZE, "⏵") { let _ = self.load_next_directory(); } - if ui::button_sized_enabled_disabled( + if self.ui_button_sized( ui, + !self.create_directory_dialog.is_open(), NAV_BUTTON_SIZE, "+", - !self.create_directory_dialog.is_open(), ) { if let Some(x) = self.current_directory() { self.create_directory_dialog.open(x.to_path_buf()); @@ -655,8 +648,7 @@ impl FileDialog { DialogMode::SaveFile => "📥 Save", }; - if ui::button_sized_enabled_disabled(ui, BUTTON_SIZE, label, self.is_selection_valid()) - { + if self.ui_button_sized(ui, self.is_selection_valid(), BUTTON_SIZE, label) { match &self.mode { DialogMode::SelectDirectory | DialogMode::SelectFile => { // self.selected_item should always contain a value, @@ -863,6 +855,23 @@ impl FileDialog { self.system_disks = disks; } + /// Helper function to add a sized button that can be enabled or disabled + fn ui_button_sized( + &self, + ui: &mut egui::Ui, + enabled: bool, + size: egui::Vec2, + label: &str, + ) -> bool { + let mut clicked = false; + + ui.add_enabled_ui(enabled, |ui| { + clicked = ui.add_sized(size, egui::Button::new(label)).clicked(); + }); + + clicked + } + /// Resets the dialog to use default values. /// Configuration variables such as `initial_directory` are retained. fn reset(&mut self) { diff --git a/src/lib.rs b/src/lib.rs index ed303999..a9d556d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,4 +58,3 @@ pub use file_dialog::{DialogMode, DialogState, FileDialog}; mod create_directory_dialog; mod data; -mod ui; diff --git a/src/ui.rs b/src/ui.rs deleted file mode 100644 index 173533ab..00000000 --- a/src/ui.rs +++ /dev/null @@ -1,43 +0,0 @@ -/// Adds a dynamically sized button to the UI that can be enabled or disabled. -/// Returns true if the button is clicked. Otherwise None is returned. -pub fn button_enabled_disabled(ui: &mut egui::Ui, text: &str, enabled: bool) -> bool { - if !enabled { - let button = egui::Button::new(text) - .stroke(egui::Stroke::NONE) - .fill(get_disabled_fill_color(ui)); - - let _ = ui.add(button); - - return false; - } - - ui.add(egui::Button::new(text)).clicked() -} - -/// Adds a fixed sized button to the UI that can be enabled or disabled. -/// Returns true if the button is clicked. Otherwise None is returned. -pub fn button_sized_enabled_disabled( - ui: &mut egui::Ui, - size: egui::Vec2, - text: &str, - enabled: bool, -) -> bool { - if !enabled { - let button = egui::Button::new(text) - .stroke(egui::Stroke::NONE) - .fill(get_disabled_fill_color(ui)); - - let _ = ui.add_sized(size, button); - - return false; - } - - ui.add_sized(size, egui::Button::new(text)).clicked() -} - -/// Returns the fill color of disabled buttons -#[inline] -fn get_disabled_fill_color(ui: &egui::Ui) -> egui::Color32 { - let c = ui.style().visuals.widgets.noninteractive.bg_fill; - egui::Color32::from_rgba_premultiplied(c.r(), c.g(), c.b(), 100) -} From 8be452e3422ff761670fccb25ebab07a0dda4426 Mon Sep 17 00:00:00 2001 From: Jannis Date: Mon, 5 Feb 2024 22:51:11 +0100 Subject: [PATCH 17/21] Add function to enable or disable the title bar (#23) * Add function to toggle the title bar * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/file_dialog.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6488b87a..3b6489f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Added `FileDialog::id` to set the ID of the window [#16](https://github.com/fluxxcode/egui-file-dialog/pull/16) - Added `FileDialog::fixed_pos` and `FileDialog::default_pos` to set the position of the window [#17](https://github.com/fluxxcode/egui-file-dialog/pull/17) - Added `FileDialog::min_size` and `FileDialog::max_size` to set the minimum and maximum size of the window [#21](https://github.com/fluxxcode/egui-file-dialog/pull/21) +- Added `FileDialog::title_bar` to enable or disable the title bar of the window [#23](https://github.com/fluxxcode/egui-file-dialog/pull/23) ### 🐛 Bug Fixes - Fixed issue where no error message was displayed when creating a folder [#18](https://github.com/fluxxcode/egui-file-dialog/pull/18) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index cfb44a91..d9007a12 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -117,6 +117,8 @@ pub struct FileDialog { window_resizable: bool, /// If the window is movable window_movable: bool, + /// If the title bar of the window is shown + window_title_bar: bool, /// The dialog that is shown when the user wants to create a new directory. create_directory_dialog: CreateDirectoryDialog, @@ -173,6 +175,7 @@ impl FileDialog { window_anchor: None, window_resizable: true, window_movable: true, + window_title_bar: true, create_directory_dialog: CreateDirectoryDialog::new(), @@ -265,6 +268,12 @@ impl FileDialog { self } + /// Sets if the title bar of the window is shown. + pub fn title_bar(mut self, title_bar: bool) -> Self { + self.window_title_bar = title_bar; + self + } + /// Sets the default file name when opening the dialog in `DialogMode::SaveFile` mode. pub fn default_file_name(mut self, name: &str) -> Self { self.default_file_name = name.to_string(); @@ -418,6 +427,7 @@ impl FileDialog { .min_size(self.window_min_size) .resizable(self.window_resizable) .movable(self.window_movable) + .title_bar(self.window_title_bar) .collapsible(false); if let Some(id) = self.window_id { From 45714858ce3512f4283b58804d639c7f0fdd1109 Mon Sep 17 00:00:00 2001 From: Jannis Date: Tue, 6 Feb 2024 19:04:41 +0100 Subject: [PATCH 18/21] Update egui (#24) * Update egui to 0.26.0 * Update CHANGELOG.md --- CHANGELOG.md | 3 +++ Cargo.toml | 2 +- examples/sandbox/Cargo.toml | 2 +- examples/save_file/Cargo.toml | 2 +- examples/select_directory/Cargo.toml | 2 +- examples/select_file/Cargo.toml | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b6489f3..d1193c9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) - Use `ui.add_enabled` instead of custom `ui.rs` module [#22](https://github.com/fluxxcode/egui-file-dialog/pull/22) +#### Dependency updates: +- Updated egui to version `0.26.0` [#24](https://github.com/fluxxcode/egui-file-dialog/pull/24) + ### 📚 Documentation - Fix syntax highlighting on crates.io [#9](https://github.com/fluxxcode/egui-file-dialog/pull/9) - Added dependency badge to `README.md` [#10](https://github.com/fluxxcode/egui-file-dialog/pull/10) diff --git a/Cargo.toml b/Cargo.toml index e75975d4..b294da85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ license = "MIT" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -egui = "0.25.0" +egui = "0.26.0" # Used to fetch user folders directories = "5.0" diff --git a/examples/sandbox/Cargo.toml b/examples/sandbox/Cargo.toml index 17fe3629..5d1ec0e4 100644 --- a/examples/sandbox/Cargo.toml +++ b/examples/sandbox/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -eframe = { version = "0.25.0", default-features = false, features = ["glow"] } +eframe = { version = "0.26.0", default-features = false, features = ["glow"] } egui-file-dialog = { path = "../../"} diff --git a/examples/save_file/Cargo.toml b/examples/save_file/Cargo.toml index 1cbb1a9c..2e44cd82 100644 --- a/examples/save_file/Cargo.toml +++ b/examples/save_file/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -eframe = { version = "0.25.0", default-features = false, features = ["glow"] } +eframe = { version = "0.26.0", default-features = false, features = ["glow"] } egui-file-dialog = { path = "../../"} diff --git a/examples/select_directory/Cargo.toml b/examples/select_directory/Cargo.toml index 85630c8a..66a6f762 100644 --- a/examples/select_directory/Cargo.toml +++ b/examples/select_directory/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -eframe = { version = "0.25.0", default-features = false, features = ["glow"] } +eframe = { version = "0.26.0", default-features = false, features = ["glow"] } egui-file-dialog = { path = "../../"} diff --git a/examples/select_file/Cargo.toml b/examples/select_file/Cargo.toml index a516a7e3..9636f081 100644 --- a/examples/select_file/Cargo.toml +++ b/examples/select_file/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -eframe = { version = "0.25.0", default-features = false, features = ["glow"] } +eframe = { version = "0.26.0", default-features = false, features = ["glow"] } egui-file-dialog = { path = "../../"} From d27def48f9956067edd2f596303373a429dc62b0 Mon Sep 17 00:00:00 2001 From: Jannis Date: Tue, 6 Feb 2024 20:47:18 +0100 Subject: [PATCH 19/21] Implement operation_id (#25) * Implement operation_id * Update CHANGELOG.md * Add example --- CHANGELOG.md | 2 + examples/multiple_actions/Cargo.toml | 10 +++ examples/multiple_actions/README.md | 7 ++ examples/multiple_actions/screenshot.png | Bin 0 -> 44993 bytes examples/multiple_actions/src/main.rs | 68 +++++++++++++++++++ src/file_dialog.rs | 82 +++++++++++++++++++++-- 6 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 examples/multiple_actions/Cargo.toml create mode 100644 examples/multiple_actions/README.md create mode 100644 examples/multiple_actions/screenshot.png create mode 100644 examples/multiple_actions/src/main.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index d1193c9b..ed704e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,10 @@ ## Unreleased ### 🚨 Breaking Changes - Rename `FileDialog::default_window_size` to `FileDialog::default_size` [#14](https://github.com/fluxxcode/egui-file-dialog/pull/14) +- Added attribute `operation_id` to `FileDialog::open` [#25](https://github.com/fluxxcode/egui-file-dialog/pull/25) ### ✨ Features +- Implemented `operation_id` so the dialog can be used for multiple different actions in a single view [#25](https://github.com/fluxxcode/egui-file-dialog/pull/25) - Added `FileDialog::anchor` to overwrite the window anchor [#11](https://github.com/fluxxcode/egui-file-dialog/pull/11) - Added `FileDialog::title` to overwrite the window title [#12](https://github.com/fluxxcode/egui-file-dialog/pull/12) - Added `FileDialog::resizable` to set if the window is resizable [#15](https://github.com/fluxxcode/egui-file-dialog/pull/15) diff --git a/examples/multiple_actions/Cargo.toml b/examples/multiple_actions/Cargo.toml new file mode 100644 index 00000000..e8a85dc2 --- /dev/null +++ b/examples/multiple_actions/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "multiple_actions" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +eframe = { version = "0.26.0", default-features = false, features = ["glow"] } +egui-file-dialog = { path = "../../"} diff --git a/examples/multiple_actions/README.md b/examples/multiple_actions/README.md new file mode 100644 index 00000000..fd4e2ed2 --- /dev/null +++ b/examples/multiple_actions/README.md @@ -0,0 +1,7 @@ +This example shows how you can query multiple files from the user in one view. + +``` +cargo run -p multiple_actions +``` + +![](screenshot.png) diff --git a/examples/multiple_actions/screenshot.png b/examples/multiple_actions/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..3561a21d05da26fd345f85876a3fde7602e14026 GIT binary patch literal 44993 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

~+C^958_8Xs;To%r>B-+hrQbk7Z5-Fo*< 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�oO1ds^rnZjUr)xrFNpha}I0g}$0IwjXTb#EobXq~h~ z{Ipm*D#%(+4xe`!a$8gD(J~z1sDH^`9nFuoV#DQ7^Rd4&AL{>NVGq$`i2ha}%UIC@ zs|bafB?nDG`P#OpzFK(FC~T%b)P08#gNZk1^?kFbzj|;_b&b^Sx8g1@yj+uL+m{B4>oL=k-|YTjx}7vLr`kJmd^kRh&N>H$D%*} z7}l?BF^Ll3FsejW+K5UAL1FFa=DN`e%P8e3eWF|@NrY3!k_)T4wxs0R)1El7i~xbh z{Y6hf`9bI^F$q0q^r!yK17A@MzCloFovvhR4wcYy*y`sze z)=Z0RXS5Sch?zazM-}YMelt~(+*FaE-QYjqC0#=#Y&LkM(lzhuwmYs!B<0_YmU8lh z{~(&M>#2hY7_i-<85mTP63=+N)RX2W^ZonxYmeO1ZZPN(Goa|x>;6!tDqA(M&%dyO z;gi4$CN3scBcjO+9HJ+Epe^_%;^6C94JA$UEKIA11hnq|?)nsQR!l;p+TJdS%g6IM zVCq#e9 zJP;ml?3ro))zha+! z%lY+fAukw2C0SHdbUxsFsRJu&1S*6J4D{Od(M${++O%Yxo=*{l4(bfC|9N}=tshwl zDx8-^nnI_?D-e`hYk0K+XZ$x=lkqJzWBSI8XM^@h1C^XFMSEz=Q4cOs zX$`EqFNam;SlH-UwadJ|!~A>BOl|GROP7k31M00VII_%B@xor!yG>7;T?L#X_OlHE z)E6Bm3PeUAWYm8BHBI3}U|$7=Pru$?F?MRK;ms_~)`xQ0qBLpJ5{2}k+p1f3dMv*+ zLdkIJ)+Fs;*@O0@-J)R9HZTa(&M=`4FSTVlO8f~@oXZ_}El->;I6{BmrCj-2vGXPA z64~C-3G!6xc8EGzfM0CJp|=qwKFg*UL>x9^L^i#?%J}iEBycIYl)F+A zdJe%if;&|^n9!s14BzkQ*C^qrlCcUDF7mGn8`<5vci+8tZ<|*0mpg8+E2^t=&}w=z zDP@ic@{OHcL|9t) zxr0UivHWV}%Uks8GY*BVSeFs6VT5S~`q+(}TqVVJ(x@V^ES287SqHy1c3z@(Jbo~5 z1^drGoYt3LuaIcWq{ep2CdYfjY)h_Pm1tv7m%G$at zKwDudf-|T-eYUfL$@rI7^;^V1;}!IABV@ZvnTxe^{-#9jp-(dkQlV&Chf<~cC*MnSVrsl_Voa}==PmHG%2DJprr9k9?Bl)%1 zx^;WbpX9KZtb>^72N&P??$HFk z6`-g`UQL!RmgQ85Y}hh51H9?#>S`0|0VM`D;D0ccgF_uFbeynU?K*d!g^C@YEMGWw zk7CU;Ek(dw{d4Ee9Rw%?_7LqMYm&M zA}n%Za9|Tc8ur(SrF4h{T1$k9FdaB;3INVbhT$*FG{lMzpTun-fmAoZC`VE6g5jma z5wr!(4;E$@-`13%$<~~g%Mn64UC6q6^|;nB7mn}@qMc1?dMEQ08BYYz!P5Zxp#g0r z>h%5)j>Ch00+pu5+FrD#wS**ws$meI7itB_y#0aZi%p^omb3B(Y-WFY;)Ap@8FO*X z7NLSB7Sr4Ara2r4Z6po?IzuT5Sq_m9Pv*c|T^P)6#<33u1q4^|BT>wvoDv53I5bF0 z6kI>*@3+)4rVE6*)azbNPH?LC!u0FwnvBe2BPVP{YSqNq9+8`}wTtNaMF z;!`MmF)0AbK^hbNdHTSlmcu)fH6lD{z8{o#8_+o9;sr9l#mQ0fD`)heBae*pOEI+A z7VsXV51X%T_bX;F$b{D!RlivJ#~To%aJ)xak&C%IR$}o1lJ}YU7a57b3xLOE_-3&a zHQy{6UUcxHbcaQX(8#U_oF!A1EsMxK5_fEvuFeK>wNP}>e6xj{Tfi`ow1jYU7TZx# zYy8rg!z>Q$yDjhxZFvd1XGLzydcNx%gI$Yzgxrlq6#~=?xyC0ABN)uNTdNL?=RFNuADE70ol4@ zn`*JM#0IA~uVZ?hW;MsrKLrc~t6WaMN^3F|2!O~UJ|vk0wx$fbsdyr@y5SO2ZW!-U zJPRx{994y=6|9R9kRId?Y!V_}yk_*689nQMvGyZEu)Mtdq)jipF6*gC4>?pVy$4a> zH0}|>@^a;xPHm=V{>FldPABiTr@OmyWhw&YJkQTV+HF@qJ}mvdJ6~p?4ycNL72M#t zSPDl!%e=a<^hVV%kXr+ZolV`-Xdp_yMT>;Rh{2wbW9lR!AT%gZz)4-qs^-&ii{=eH zCgMF-NB&8Ht#l1a_Dqc9AaV~CWcPAc;QpMN^q+st$_$pEw2b6pQx{OnDp;Nz+tRYk zT@DAptDVAN?*m}#evQxCfK&EK7w zE^V*%{k=cSO8po|HA&qvS96yqr%LFWpuIyXZ8to<>@BtiGPiz}vp~x$g&stRvp_W^ zW6oQADO@=JIk=WxMD@0Ar+PjpSyjGf%^C^=A0#XkX49h#Agz8+Pk(BeXb5{1|GhGv z9Q%9G8ur)^D!ThL-L)^|Q$ZS+-!1_tM#p(6z>-P(L9$=$N5_PMnOUkmdCmk8ySxvmzn#ef zG4nAj`1)hz$U3B6`imB|qN)bDjc7X@y$~pKMDhH=jyl;({zY2{P4*MXKA2>D%yS0D zXzJq?p$zyXBn+2h+Fk$#MNOyPQDW9FN@h@bjbcDk^CDAI*W%J;%NX7u482?~yCqfQ z*;W7ew&^DZJ`jCsS0%srv{n7=aC_)p*M7$@KN`|q?-RE-_3zNB)66+@{1zMK&1lKF z$0cCNbzSyYTrN$xr*M1V2Fou;l82rzf7oPkhGLsgAclTH{69Gw_j1G^v)rvV&tC3O zeA>pmFt*JnHH_TYjLVK_nF0n*`ekpb)0)FM^Losj8L_keXu#0(ai6wsIoP|yfj$%i zUGMFz3%UN(|IkB~`23WKFNdU*O0OR`jesM2aftV-q*N~t*UBw4l{V* zgt)unX#`|1E~|_wxp(im?f7%?E}O^Ktu5rywEbQn{|D3qsR;A{UB|kKj}P9lM+Y+S zSahCs0-0;c@FDvyF%ruxfDl20?udrR!@;8R_Ogh^MEJHLDMOFfy(nB9G4JGb^k5K2 zP#w(4kKavU<#nA?F@Sal$^u|~|3xjJ+v=SAs&Oj}6#%`+T*HG)*RNv1NW2t6B z-A?-p7o87_|Mte_`sTcQkJ4w?OGS3-*=l*A3J-V2jL1HEW=b_7mrNUbrhZK|AE7EF z3j+f?1ZjWu{Z^*=Akx~f%jKILB&D+3_PpNR@>kM7-%xs~;`lgR_o>o`Z@%JLcP;6# zZ1M^9iN=rDs>)aPYy5PJlM^ZCX&KGD*b%5QwW__TcUxr5yuPpT_!kg>696F2XR&zWyC>omoW zb}aR|vWortqSvEeB~A$G@0XFCS-MsH2dAVjH~TfcpP;!Qf5WX2V?GToD()n{yI0PQ hWttVr)e7I1Rm~pyv?$UhR>Cixne%4EO*7yA{{fO5E1>`Y literal 0 HcmV?d00001 diff --git a/examples/multiple_actions/src/main.rs b/examples/multiple_actions/src/main.rs new file mode 100644 index 00000000..44c620cd --- /dev/null +++ b/examples/multiple_actions/src/main.rs @@ -0,0 +1,68 @@ +use std::path::PathBuf; + +use eframe::egui; +use egui_file_dialog::{DialogMode, FileDialog}; + +struct MyApp { + file_dialog: FileDialog, + + selected_file_a: Option, + selected_file_b: Option, +} + +impl MyApp { + pub fn new(_cc: &eframe::CreationContext) -> Self { + Self { + file_dialog: FileDialog::new().id("egui_file_dialog"), + + selected_file_a: None, + selected_file_b: None, + } + } +} + +impl eframe::App for MyApp { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + egui::CentralPanel::default().show(ctx, |ui| { + if ui.button("Select file a").clicked() { + let _ = self + .file_dialog + .open(DialogMode::SelectFile, true, Some("select_a")); + } + + if ui.button("Select file b").clicked() { + let _ = self + .file_dialog + .open(DialogMode::SelectFile, true, Some("select_b")); + } + + ui.label(format!("Selected file a: {:?}", self.selected_file_a)); + ui.label(format!("Selected file b: {:?}", self.selected_file_b)); + + self.file_dialog.update(ctx); + + if let Some(path) = self.file_dialog.selected() { + if self.file_dialog.operation_id() == Some("select_a") { + self.selected_file_a = Some(path.to_path_buf()); + } + + if self.file_dialog.operation_id() == Some("select_b") { + self.selected_file_b = Some(path.to_path_buf()); + } + } + }); + } +} + +fn main() -> eframe::Result<()> { + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default().with_inner_size([1080.0, 720.0]), + ..Default::default() + }; + + eframe::run_native( + "My egui application", + options, + Box::new(|ctx| Box::new(MyApp::new(ctx))), + ) +} diff --git a/src/file_dialog.rs b/src/file_dialog.rs index d9007a12..f1297547 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -71,6 +71,12 @@ pub struct FileDialog { /// If files are displayed in addition to directories. /// This option will be ignored when mode == DialogMode::SelectFile. show_files: bool, + /// This is an optional ID that can be set when opening the dialog to determine which + /// operation the dialog is used for. This is useful if the dialog is used multiple times + /// for different actions in the same view. The ID then makes it possible to distinguish + /// for which action the user has selected an item. + /// This ID is not used internally. + operation_id: Option, /// The user directories like Home or Documents. /// These are loaded once when the dialog is created or when the refresh() method is called. @@ -155,6 +161,7 @@ impl FileDialog { state: DialogState::Closed, initial_directory: std::env::current_dir().unwrap_or_default(), show_files: true, + operation_id: None, user_directories: UserDirectories::new(), system_disks: Disks::new_with_refreshed_list(), @@ -286,8 +293,64 @@ impl FileDialog { /// /// Returns the result of the operation to load the initial directory. /// - /// The `show_files` parameter will be ignored when the mode equals `DialogMode::SelectFile`. - pub fn open(&mut self, mode: DialogMode, mut show_files: bool) -> io::Result<()> { + /// If you don't need to set the individual parameters, you can also use the shortcut + /// methods `select_directory`, `select_file` and `save_file`. + /// + /// # Arguments + /// + /// * `mode` - The mode in which the dialog should be opened + /// * `show_files` - If files should also be displayed to the user in addition to directories. + /// This is ignored if the mode is `DialogMode::SelectFile`. + /// * `operation_id` - Sets an ID for which operation the dialog was opened. + /// This is useful when the dialog can be used for various operations in a single view. + /// The ID can then be used to check which action the user selected an item for. + /// + /// # Examples + /// + /// The following example shows how the dialog can be used for multiple + /// actions using the `operation_id``. + /// + /// ``` + /// use std::path::PathBuf; + /// + /// use egui_file_dialog::{DialogMode, FileDialog}; + /// + /// struct MyApp { + /// file_dialog: FileDialog, + /// + /// selected_file_a: Option, + /// selected_file_b: Option, + /// } + /// + /// impl MyApp { + /// fn update(&mut self, ctx: &egui::Context, ui: &mut egui::Ui) { + /// if ui.button("Select file a").clicked() { + /// let _ = self.file_dialog.open(DialogMode::SelectFile, true, Some("select_a")); + /// } + /// + /// if ui.button("Select file b").clicked() { + /// let _ = self.file_dialog.open(DialogMode::SelectFile, true, Some("select_b")); + /// } + /// + /// self.file_dialog.update(ctx); + /// + /// if let Some(path) = self.file_dialog.selected() { + /// if self.file_dialog.operation_id() == Some("select_a") { + /// self.selected_file_a = Some(path.to_path_buf()); + /// } + /// if self.file_dialog.operation_id() == Some("select_b") { + /// self.selected_file_b = Some(path.to_path_buf()); + /// } + /// } + /// } + /// } + /// ``` + pub fn open( + &mut self, + mode: DialogMode, + mut show_files: bool, + operation_id: Option<&str>, + ) -> io::Result<()> { self.reset(); // Try to use the parent directory if the initial directory is a file. @@ -311,6 +374,7 @@ impl FileDialog { self.mode = mode; self.state = DialogState::Open; self.show_files = show_files; + self.operation_id = operation_id.map(String::from); if let Some(title) = &self.window_overwrite_title { self.window_title = title.clone(); @@ -333,7 +397,7 @@ impl FileDialog { /// /// The function ignores the result of the initial directory loading operation. pub fn select_directory(&mut self) { - let _ = self.open(DialogMode::SelectDirectory, false); + let _ = self.open(DialogMode::SelectDirectory, false, None); } /// Shortcut function to open the file dialog to prompt the user to select a file. @@ -342,7 +406,7 @@ impl FileDialog { /// /// The function ignores the result of the initial directory loading operation. pub fn select_file(&mut self) { - let _ = self.open(DialogMode::SelectFile, false); + let _ = self.open(DialogMode::SelectFile, false, None); } /// Shortcut function to open the file dialog to prompt the user to save a file. @@ -351,7 +415,7 @@ impl FileDialog { /// /// The function ignores the result of the initial directory loading operation. pub fn save_file(&mut self) { - let _ = self.open(DialogMode::SaveFile, true); + let _ = self.open(DialogMode::SaveFile, true, None); } /// Returns the mode the dialog is currently in. @@ -375,6 +439,13 @@ impl FileDialog { } } + /// Returns the ID of the operation for which the dialog is currently being used. + /// + /// See `FileDialog::open` more information. + pub fn operation_id(&self) -> Option<&str> { + self.operation_id.as_deref() + } + /// The main update method that should be called every frame if the dialog is to be visible. /// /// This function has no effect if the dialog state is currently not `DialogState::Open`. @@ -887,6 +958,7 @@ impl FileDialog { fn reset(&mut self) { self.state = DialogState::Closed; self.show_files = true; + self.operation_id = None; self.system_disks = Disks::new_with_refreshed_list(); From b3f773b7064a34d042079f9a218978fc59b7bb42 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 7 Feb 2024 19:50:58 +0100 Subject: [PATCH 20/21] Fix loading disk on Windows (#26) * Fix disk can be pressed multiple times on windows * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/file_dialog.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed704e3c..42cb3ad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### 🐛 Bug Fixes - Fixed issue where no error message was displayed when creating a folder [#18](https://github.com/fluxxcode/egui-file-dialog/pull/18) +- Fixed an issue where the same disk can be loaded multiple times in a row on Windows [#26](https://github.com/fluxxcode/egui-file-dialog/pull/26) ### 🔧 Changes - Removed the version of `egui-file-dialog` in the examples [#8](https://github.com/fluxxcode/egui-file-dialog/pull/8) diff --git a/src/file_dialog.rs b/src/file_dialog.rs index f1297547..89105a88 100644 --- a/src/file_dialog.rs +++ b/src/file_dialog.rs @@ -1125,10 +1125,18 @@ impl FileDialog { /// The function deletes all directories from the `directory_stack` that are currently /// stored in the vector before the `directory_offset`. fn load_directory(&mut self, path: &Path) -> io::Result<()> { + let full_path = match fs::canonicalize(path) { + Ok(path) => path, + Err(err) => { + self.directory_error = Some(err.to_string()); + return Err(err); + } + }; + // Do not load the same directory again. // Use reload_directory if the content of the directory should be updated. if let Some(x) = self.current_directory() { - if x == path { + if x == full_path { return Ok(()); } } @@ -1138,14 +1146,6 @@ impl FileDialog { .drain(self.directory_stack.len() - self.directory_offset..); } - let full_path = match fs::canonicalize(path) { - Ok(path) => path, - Err(err) => { - self.directory_error = Some(err.to_string()); - return Err(err); - } - }; - self.directory_stack.push(full_path); self.directory_offset = 0; From 254b471fdfef4b7aaa8f687e3defa95835c8fd66 Mon Sep 17 00:00:00 2001 From: Jannis Date: Wed, 7 Feb 2024 20:03:19 +0100 Subject: [PATCH 21/21] Prepare release v0.2.0 (#27) --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42cb3ad7..ac2b7db1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # egui-file-dialog changelog -## Unreleased +## 2024-02-07 - v0.2.0 - API improvements ### 🚨 Breaking Changes - Rename `FileDialog::default_window_size` to `FileDialog::default_size` [#14](https://github.com/fluxxcode/egui-file-dialog/pull/14) - Added attribute `operation_id` to `FileDialog::open` [#25](https://github.com/fluxxcode/egui-file-dialog/pull/25) diff --git a/Cargo.toml b/Cargo.toml index b294da85..22f1949b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ [package] name = "egui-file-dialog" description = "An easy-to-use file dialog for egui" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["fluxxcode"] repository = "https://github.com/fluxxcode/egui-file-dialog" diff --git a/README.md b/README.md index 6ade02a3..5faf7af4 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Cargo.toml: ```toml [dependencies] eframe = "0.25.0" -egui-file-dialog = "0.1.0" +egui-file-dialog = "0.2.0" ``` main.rs: