1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 22:00:03 -04:00

Store web_sys::File inside of DroppedFile (#8354)

* Closes #4654
* Related #4667
* [x] I have followed the instructions in the PR template

This PR avoids materializing the contents of a file that was dragged
into an egui application on the web. It does so by storing the
`web_sys::File` handle directly on WASM.

This breaks the existing API of `DroppedFile` on the web, because there
is no way to retrieve the bytes synchronously form a `DroppedFile`
anymore, forcing handling call sites to become asynchronous.

The native API remains the same.

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
Jochen Görtler
2026-08-03 17:07:37 +02:00
committed by GitHub
parent 2e7a92bc37
commit 49d4befe6b
13 changed files with 197 additions and 120 deletions

View File

@@ -19,6 +19,7 @@ workspace = true
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--generate-link-to-definition"]
targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
[lib]
@@ -90,3 +91,9 @@ document-features = { workspace = true, optional = true }
ron = { workspace = true, optional = true }
serde = { workspace = true, optional = true, features = ["derive", "rc"] }
# web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
# For `DroppedFile`, which hands web apps a file handle instead of its contents.
web-sys = { workspace = true, features = ["File"] }

View File

@@ -1,19 +1,51 @@
use std::{path::Path, sync::Arc};
#[cfg(target_arch = "wasm32")]
use std::{future::Future, pin::Pin};
/// A file dropped into egui.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct DroppedFile {
/// Set by the `egui-winit` backend.
pub path: Option<std::path::PathBuf>,
///
/// The integration owns the concrete file handle, letting egui remain independent of windowing
/// backends and file APIs.
pub trait DroppedFile: std::fmt::Debug {
/// The path of the dropped file.
///
/// This is an absolute path on native platforms. On the web, it is a relative path containing
/// only the file name because browsers do not expose the file's local path.
fn path(&self) -> &Path;
/// Name of the file. Set by the `eframe` web backend.
pub name: String,
/// Read the file contents.
///
/// This is asynchronous because browsers can only read files asynchronously.
///
/// # Errors
///
/// Returns an error if the browser cannot read the file.
#[cfg(target_arch = "wasm32")]
fn bytes_async(&self) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, String>> + '_>>;
/// With the `eframe` web backend, this is set to the mime-type of the file (if available).
pub mime: String,
/// Read the file contents.
///
/// # Errors
///
/// Returns an error if the file cannot be read.
#[cfg(not(target_arch = "wasm32"))]
fn bytes(&self) -> Result<Vec<u8>, String>;
/// Set by the `eframe` web backend.
pub last_modified: Option<std::time::SystemTime>,
/// Set by the `eframe` web backend.
pub bytes: Option<std::sync::Arc<[u8]>>,
/// The browser file handle, if this file was dropped on the web.
#[cfg(target_arch = "wasm32")]
fn web_file(&self) -> Option<&web_sys::File> {
None
}
}
/// A shared reference to a dropped file.
#[cfg(not(all(target_arch = "wasm32", target_feature = "atomics")))]
pub type DroppedFileHandle = Arc<dyn DroppedFile + Send + Sync>;
/// A shared reference to a dropped file.
///
/// This is not necessarily `Send + Sync` when wasm threads are enabled, because
/// [`web_sys::File`] is not thread-safe in that configuration.
#[cfg(all(target_arch = "wasm32", target_feature = "atomics"))]
pub type DroppedFileHandle = Arc<dyn DroppedFile>;

View File

@@ -16,7 +16,7 @@ mod touch;
mod viewport_info;
pub use self::{
dropped_file::DroppedFile,
dropped_file::{DroppedFile, DroppedFileHandle},
event::Event,
event_filter::EventFilter,
hovered_file::HoveredFile,

View File

@@ -1,6 +1,6 @@
use crate::{OrderedViewportIdMap, Theme, ViewportId, ViewportIdMap, emath::Rect};
use super::{DroppedFile, Event, HoveredFile, SafeAreaInsets, ViewportInfo};
use super::{DroppedFileHandle, Event, HoveredFile, SafeAreaInsets, ViewportInfo};
/// What the integrations provides to egui at the start of each frame.
///
@@ -13,7 +13,7 @@ use super::{DroppedFile, Event, HoveredFile, SafeAreaInsets, ViewportInfo};
///
/// Ii "points" can be calculated from native physical pixels
/// using `pixels_per_point` = [`crate::Context::zoom_factor`] * `native_pixels_per_point`;
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RawInput {
/// The id of the active viewport.
@@ -65,9 +65,20 @@ pub struct RawInput {
/// Dragged files dropped into egui.
///
/// egui never reads the file contents.
#[cfg_attr(
not(target_arch = "wasm32"),
doc = "Call [`crate::DroppedFile::bytes`] to read a dropped file."
)]
#[cfg_attr(
target_arch = "wasm32",
doc = "Call [`crate::DroppedFile::bytes_async`] to read a dropped file."
)]
///
/// Note: when using `eframe` on Windows, this will always be empty if drag-and-drop support has
/// been disabled in [`crate::viewport::ViewportBuilder`].
pub dropped_files: Vec<DroppedFile>,
#[cfg_attr(feature = "serde", serde(skip))]
pub dropped_files: Vec<DroppedFileHandle>,
/// The native window has the keyboard focus (i.e. is receiving key presses).
///