Skip to content

Commit

Permalink
Add FileDialog::{active_entry, update_with_custom_right_panel} (flu…
Browse files Browse the repository at this point in the history
…xxcode#170)

* Add FileDialog::active_entry

* Add FileDialog::update_with_custom_right_panel

* Improve doc wording for `FileDialog::active_entry`

* Rename `update_with_custom_right_panel` to `update_with_right_panel_ui`

* Add FileDialog::active_selected_entries

* Add custom-right-panel example

* Reposition `active_selected_entries` to be next to `active_entry`

* Cross-reference `active_entry` and `active_selected_entries` in the docs

* examples/custom-right-panel: Use FileDialog::mode to determine the mode

* Fix up import formatting
  • Loading branch information
crumblingstatue authored Oct 25, 2024
1 parent c11ce40 commit 07f7a2e
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 4 deletions.
10 changes: 10 additions & 0 deletions examples/custom-right-panel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "custom-right-panel"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
eframe = { workspace = true }
egui-file-dialog = { path = "../../"}
82 changes: 82 additions & 0 deletions examples/custom-right-panel/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
use std::path::PathBuf;

use eframe::egui;
use egui_file_dialog::{DialogMode, FileDialog};

struct MyApp {
file_dialog: FileDialog,
selected_items: Option<Vec<PathBuf>>,
}

impl MyApp {
pub fn new(_cc: &eframe::CreationContext) -> Self {
Self {
file_dialog: FileDialog::new(),
selected_items: 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 single").clicked() {
self.file_dialog.select_file();
}
if ui.button("Select multiple").clicked() {
self.file_dialog.select_multiple();
}

ui.label("Selected items:");

if let Some(items) = &self.selected_items {
for item in items {
ui.label(format!("{:?}", item));
}
} else {
ui.label("None");
}

self.file_dialog
.update_with_right_panel_ui(ctx, &mut |ui, dia| match dia.mode() {
DialogMode::SelectMultiple => {
ui.heading("Selected items");
ui.separator();
egui::ScrollArea::vertical()
.max_height(ui.available_height())
.show(ui, |ui| {
for item in dia.active_selected_entries() {
ui.small(format!("{item:#?}"));
ui.separator();
}
});
}
_ => {
ui.heading("Active item");
ui.small(format!("{:#?}", dia.active_entry()));
}
});

match self.file_dialog.mode() {
DialogMode::SelectMultiple => {
if let Some(items) = self.file_dialog.take_selected_multiple() {
self.selected_items = Some(items);
}
}
_ => {
if let Some(item) = self.file_dialog.take_selected() {
self.selected_items = Some(vec![item]);
}
}
}
});
}
}

fn main() -> eframe::Result<()> {
eframe::run_native(
"File dialog example",
eframe::NativeOptions::default(),
Box::new(|ctx| Ok(Box::new(MyApp::new(ctx)))),
)
}
76 changes: 72 additions & 4 deletions src/file_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ impl Debug for dyn FileDialogModal + Send + Sync {
}
}

/// Callback type to inject a custom egui ui inside the file dialog's ui.
///
/// Also gives access to the file dialog, since it would otherwise be inaccessible
/// inside the closure.
type FileDialogUiCallback<'a> = dyn FnMut(&mut egui::Ui, &mut FileDialog) + 'a;

impl FileDialog {
// ------------------------------------------------------------------------
// Creation:
Expand Down Expand Up @@ -383,7 +389,33 @@ impl FileDialog {
}

self.update_keybindings(ctx);
self.update_ui(ctx);
self.update_ui(ctx, None);

self
}

/// Do an [update](`Self::update`) with a custom right panel ui.
///
/// Example use cases:
/// - Show custom information for a file (size, MIME type, etc.)
/// - Embed a preview, like a thumbnail for an image
/// - Add controls for custom open options, like open as read-only, etc.
///
/// See [`active_entry`](Self::active_entry) to get the active directory entry
/// to show the information for.
///
/// This function has no effect if the dialog state is currently not `DialogState::Open`.
pub fn update_with_right_panel_ui(
&mut self,
ctx: &egui::Context,
f: &mut FileDialogUiCallback,
) -> &Self {
if self.state != DialogState::Open {
return self;
}

self.update_keybindings(ctx);
self.update_ui(ctx, Some(f));

self
}
Expand Down Expand Up @@ -964,6 +996,26 @@ impl FileDialog {
}
}

/// Returns the currently active directory entry.
///
/// This is either the currently highlighted entry, or the currently active directory
/// if nothing is being highlighted.
///
/// For the [`DialogMode::SelectMultiple`] counterpart,
/// see [`FileDialog::active_selected_entries`].
pub const fn active_entry(&self) -> Option<&DirectoryEntry> {
self.selected_item.as_ref()
}

/// Returns an iterator over the currently selected entries in [`SelectMultiple`] mode.
///
/// For the counterpart in single selection modes, see [`FileDialog::active_entry`].
///
/// [`SelectMultiple`]: DialogMode::SelectMultiple
pub fn active_selected_entries(&self) -> impl Iterator<Item = &DirectoryEntry> {
self.get_dir_content_filtered_iter().filter(|p| p.selected)
}

/// Returns the ID of the operation for which the dialog is currently being used.
///
/// See `FileDialog::open` for more information.
Expand All @@ -985,7 +1037,13 @@ impl FileDialog {
/// UI methods
impl FileDialog {
/// Main update method of the UI
fn update_ui(&mut self, ctx: &egui::Context) {
///
/// Takes an optional callback to show a custom right panel.
fn update_ui(
&mut self,
ctx: &egui::Context,
right_panel_fn: Option<&mut FileDialogUiCallback>,
) {
let mut is_open = true;

if self.config.as_modal {
Expand Down Expand Up @@ -1017,6 +1075,17 @@ impl FileDialog {
});
}

// Optionally, show a custom right panel (see `update_with_custom_right_panel`)
if let Some(f) = right_panel_fn {
egui::SidePanel::right(self.window_id.with("right_panel"))
// Unlike the left panel, we have no control over the contents, so
// we don't restrict the width. It's up to the user to make the UI presentable.
.resizable(true)
.show_inside(ui, |ui| {
f(ui, self);
});
}

egui::TopBottomPanel::bottom(self.window_id.with("bottom_panel"))
.resizable(false)
.show_inside(ui, |ui| {
Expand Down Expand Up @@ -2480,8 +2549,7 @@ impl FileDialog {
}
DialogMode::SelectMultiple => {
let result: Vec<PathBuf> = self
.get_dir_content_filtered_iter()
.filter(|p| p.selected)
.active_selected_entries()
.map(crate::DirectoryEntry::to_path_buf)
.collect();

Expand Down

0 comments on commit 07f7a2e

Please sign in to comment.