Skip to content

Commit

Permalink
feat: implement information view and preview for files (#184)
Browse files Browse the repository at this point in the history
* initial implementation of file information display

* revert previous prototype and create information_panel.rs

* add meta data grid

* update meta_data

* revert public config and selected item

* only read 1000 characters of text files and implement Markdown as an example

* add separators

* introduce info_panel feature

* upload test_image

* implement metadata, markdown and image preview

* metadata in ScrollArea and only display size for files, not directories

* clean up, add doc and add folder icon as preview

* removed profiles, not used anymore

* image crate needs to be loaded always, because of the Image metadata loaded in directory_content.rs

* only load image crate when using info_panel feature

* only load text content once. show icon if no preview is available

* extend preview for jpeg and jpg

* extension to lowercase, so it does not matter how they are stored on the file system. added color space

* rapidly decreased loading times of directories using image-meta crate instead of image crate

* indexmap instead of hashmap, so that metadata is always in the same order

* cargo clippy

* remove unused doc

* Update Cargo.toml

Co-authored-by: Nicolas <[email protected]>

* workflow fix

* reformat

* text content is now only loaded for the selected file and then stored in the ActiveDirectory

* not hard-coding max chars of text preview

* fix width and compilation errors

* implement custom metadata loader

* remove unused dependency

* code review by bircni

* required for image loader

* add for cargo machete. image crate is required per documentation of egui loaders

* remove md render

* use picked instead of selected

* rename example

* rename example

* create metadata struct

* rustfmt

* implement content_mut

* fix typo

* implement `InfoPanelEntry` struct

* change feature name to information_view

* move format_pixels() to information_panel.rs

* add screenshot and remove test_image

* remove print

* change date formatting

* Update examples/select_file_with_information_view/README.md

Co-authored-by: Jannis <[email protected]>

* Update src/data/information_panel.rs

Co-authored-by: Jannis <[email protected]>

* Update src/data/information_panel.rs

Co-authored-by: Jannis <[email protected]>

* add docs

* introduced path_buf variable

* code review

* rename handler

* set image max width to be as high as the panel is wide

* show icon for all files

* don't set width of meta grid

* adapt for latest example structure

* Revert "don't set width of meta grid"

This reverts commit 0c4eb84.

* add comment for image crate

* clippy

* remove images if more than 10 are loaded

* move information_panel.rs

* rustfmt

* better doc for example

* update README.md

* implement `forget_all_stored_images`

* implement `forget_all_stored_images` in `MyApp::update()`

* update Cargo.toml

* Update examples/README.md

Co-authored-by: Jannis <[email protected]>

* pick file rename

* remove hardcoded icon width

* id with `FileDialog` as parent

* fix id

* windows should always take up the same amount of space

* Update src/information_panel.rs

Co-authored-by: Jannis <[email protected]>

* Update src/information_panel.rs

Co-authored-by: Jannis <[email protected]>

* extract image display function

* rustfmt

* all previews should have the same size

* Update examples/pick_file_with_information_view.rs

Co-authored-by: Jannis <[email protected]>

* Update Cargo.toml

Co-authored-by: Jannis <[email protected]>

* fix typo

---------

Co-authored-by: Nicolas <[email protected]>
Co-authored-by: Jannis <[email protected]>
  • Loading branch information
3 people authored Dec 5, 2024
1 parent 042efb2 commit 300cb6f
Show file tree
Hide file tree
Showing 10 changed files with 630 additions and 11 deletions.
13 changes: 12 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,28 @@ dunce = "1.0.5"
sysinfo = { version = "0.32", default-features = false, features = ["disk"] }
# persistent storage
serde = { version = "1", features = ["derive"], optional = true }
# meta-data storage
indexmap = { version = "2.6.0", features = ["serde"], optional = true }

# info panel meta-data display
image-meta = { version = "0.1.2", optional = true }
chrono = { version = "0.4.38", optional = true }

[dev-dependencies]
eframe = { version = "0.29.1", default-features = false, features = [
"glow",
"persistence",
] }
egui-file-dialog = { path = "." }
egui-file-dialog = { path = "." , features = ["information_view"] }
egui_extras = { version = "0.29", features = ["all_loaders"] }
# required by the egui loaders
image = { version = "0.25.5", features = ["bmp", "jpeg", "gif", "png", "tiff", "rayon"] }

[features]
default = ["serde", "default_fonts"]
serde = ["dep:serde"]
default_fonts = ["egui/default_fonts"]
information_view = ["dep:chrono", "image-meta", "indexmap"]

[lints.rust]
unsafe_code = "warn"
Expand All @@ -54,3 +64,4 @@ struct_field_names = { level = "allow", priority = 13 }
missing_fields_in_debug = { level = "allow", priority = 14 }
missing_errors_doc = { level = "allow", priority = 15 }
module_name_repetitions = { level = "allow", priority = 16 }
cast_precision_loss = { level = "allow", priority = 17 }
22 changes: 22 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,25 @@ cargo run --example save_file
```

![Screenshot](../media/examples/save_file.png)


## Pick File with Information View

Example showing how to pick a file and display file information using the `InformationView`.

Requires the feature `information_view` as well as these dependencies:

```toml
[dependencies]
egui-file-dialog = { version = "*", features = ["information_view"] }
egui_extras = { version = "0.29", features = ["all_loaders"] }
# required by the egui loaders
image = { version = "0.25.5", features = ["bmp", "jpeg", "gif", "png", "tiff", "rayon"] }
```

```shell
cargo run --example pick_file_with_information_view
```

![Screenshot](../media/examples/information_view.png)

78 changes: 78 additions & 0 deletions examples/pick_file_with_information_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use std::path::PathBuf;

use eframe::egui;
use egui_file_dialog::information_panel::InformationPanel;
use egui_file_dialog::{DialogState, FileDialog};

struct MyApp {
file_dialog: FileDialog,
information_panel: InformationPanel,
selected_file: Option<PathBuf>,
}

impl MyApp {
pub fn new(_cc: &eframe::CreationContext) -> Self {
Self {
file_dialog: FileDialog::new(),
information_panel: InformationPanel::default()
.add_file_preview("csv", |ui, item| {
ui.label("CSV preview:");
if let Some(mut content) = item.content() {
egui::ScrollArea::vertical()
.max_height(ui.available_height())
.show(ui, |ui| {
ui.add(egui::TextEdit::multiline(&mut content).code_editor());
});
}
})
// add additional metadata loader
.add_metadata_loader("pdf", |other_meta_data, path| {
// as a simple example, just show the Filename of the PDF
other_meta_data.insert("PDF Filename".to_string(), format!("{path:?}"));
}),
selected_file: 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").clicked() {
self.file_dialog.pick_file();
}

self.file_dialog.set_right_panel_width(200.0);

if let Some(path) = self
.file_dialog
.update_with_right_panel_ui(ctx, &mut |ui, dia| {
self.information_panel.ui(ui, dia);
})
.picked()
{
self.selected_file = Some(path.to_path_buf());
}

match self.file_dialog.state() {
DialogState::Closed | DialogState::Cancelled => {
self.information_panel.forget_all_stored_images(ui);
}
_ => {}
}

ui.label(format!("Selected file: {:?}", self.selected_file));
});
}
}

fn main() -> eframe::Result<()> {
eframe::run_native(
"File dialog example",
eframe::NativeOptions::default(),
Box::new(|ctx| {
egui_extras::install_image_loaders(&ctx.egui_ctx);
Ok(Box::new(MyApp::new(ctx)))
}),
)
}
Binary file added media/examples/information_view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ pub struct FileDialogConfig {
/// If the search input in the top panel should be visible.
pub show_search: bool,

/// Set the width of the right panel, if used
pub right_panel_width: Option<f32>,

/// If the sidebar with the shortcut directories such as
/// “Home”, “Documents” etc. should be visible.
pub show_left_panel: bool,
Expand Down Expand Up @@ -260,6 +263,7 @@ impl Default for FileDialogConfig {
show_system_files_option: true,
show_search: true,

right_panel_width: None,
show_left_panel: true,
show_pinned_folders: true,
show_places: true,
Expand Down
34 changes: 29 additions & 5 deletions src/data/directory_content.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
use crate::config::{FileDialogConfig, FileFilter};
use egui::mutex::Mutex;
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc};
use std::time::SystemTime;
use std::{fs, io, thread};

use egui::mutex::Mutex;

use crate::config::{FileDialogConfig, FileFilter};

/// Contains the metadata of a directory item.
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct Metadata {
pub size: Option<u64>,
pub last_modified: Option<SystemTime>,
pub created: Option<SystemTime>,
pub file_type: Option<String>,
}

/// Contains the information of a directory item.
///
/// This struct is mainly there so that the metadata can be loaded once and not that
/// This struct is mainly there so that the information and metadata can be loaded once and not that
/// a request has to be sent to the OS every frame using, for example, `path.is_file()`.
#[derive(Debug, Default, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DirectoryEntry {
path: PathBuf,
metadata: Metadata,
is_directory: bool,
is_system_file: bool,
icon: String,
Expand All @@ -25,15 +34,30 @@ pub struct DirectoryEntry {
impl DirectoryEntry {
/// Creates a new directory entry from a path
pub fn from_path(config: &FileDialogConfig, path: &Path) -> Self {
let mut metadata = Metadata::default();

if let Ok(md) = fs::metadata(path) {
metadata.size = Some(md.len());
metadata.last_modified = md.modified().ok();
metadata.created = md.created().ok();
metadata.file_type = Some(format!("{:?}", md.file_type()));
}

Self {
path: path.to_path_buf(),
metadata,
is_directory: path.is_dir(),
is_system_file: !path.is_dir() && !path.is_file(),
icon: gen_path_icon(config, path),
selected: false,
}
}

/// Returns the metadata of the directory entry.
pub const fn metadata(&self) -> &Metadata {
&self.metadata
}

/// Checks if the path of the current directory entry matches the other directory entry.
pub fn path_eq(&self, other: &Self) -> bool {
other.as_path() == self.as_path()
Expand Down
1 change: 1 addition & 0 deletions src/data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ mod disks;
pub use disks::{Disk, Disks};

mod user_directories;

pub use user_directories::UserDirectories;
28 changes: 23 additions & 5 deletions src/file_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,16 @@ impl FileDialog {
self
}

/// Sets the width of the right panel.
pub fn set_right_panel_width(&mut self, width: f32) {
self.config.right_panel_width = Some(width);
}

/// Clears the width of the right panel by setting it to None.
pub fn clear_right_panel_width(&mut self) {
self.config.right_panel_width = None;
}

/// Do an [update](`Self::update`) with a custom right panel ui.
///
/// Example use cases:
Expand Down Expand Up @@ -1168,6 +1178,11 @@ impl FileDialog {
pub fn state(&self) -> DialogState {
self.state.clone()
}

/// Get the window Id
pub const fn get_window_id(&self) -> egui::Id {
self.window_id
}
}

/// UI methods
Expand Down Expand Up @@ -1213,13 +1228,16 @@ 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"))
let mut right_panel = 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);
});
.resizable(true);
if let Some(width) = self.config.right_panel_width {
right_panel = right_panel.default_width(width);
}
right_panel.show_inside(ui, |ui| {
f(ui, self);
});
}

egui::TopBottomPanel::bottom(self.window_id.with("bottom_panel"))
Expand Down
Loading

0 comments on commit 300cb6f

Please sign in to comment.