Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: File dialog handling #2032

Open
vihdzp opened this issue Apr 27, 2021 · 16 comments
Open

Feature request: File dialog handling #2032

vihdzp opened this issue Apr 27, 2021 · 16 comments
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible

Comments

@vihdzp
Copy link

vihdzp commented Apr 27, 2021

What problem does this solve or what need does it fill?

Native open/save file dialogs are surprisingly hard to do in Rust, and Bevy makes them even harder. The only crate I'm aware is able to show file dialogs across all main platforms is rfd. However, due to some bug in winit, a dependency of Bevy, only asynchronous file dialogs seem to work (see the discussion in this issue). Getting asynchronous code to work with Bevy would probably require either changing some Bevy internals, or fixing code further down the dependency chain.

In other words, file dialogs are currently very hard in Bevy, and having them already implemented would be very convenient.

What solution would you like?

If possible, I'd like that there were something like a resource that somehow handled opening a file dialog across platforms and returning its result. It could be gated behind a feature if this doesn't work across all platforms, or requires many extra dependencies. If this turns out to be infeasible, an optional plugin for a window simulating a file dialog would still be very useful.

What alternative(s) have you considered?

I considered using other dependencies, but this doesn't seem to work due to the reasons outlined above.

@vihdzp vihdzp added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Apr 27, 2021
@Moxinilian Moxinilian added A-UI Graphical user interfaces, styles, layouts, and widgets and removed S-Needs-Triage This issue needs to be labelled labels Apr 27, 2021
@Moxinilian
Copy link
Member

Moxinilian commented Apr 27, 2021

A good solution to native file browsing dialogs is necessary. However, to solve your immediate problem, have you tried polling the future manually in a system? It does not look good but it may solve your problem.

@FrankenApps
Copy link

There is also tinyfiledialogs which is not pure Rust (wraps a C library) but I found it to work reliable.
It also shouldn't be too hard to port the underlying C code to Rust.

@vihdzp
Copy link
Author

vihdzp commented Apr 28, 2021

I can't figure out a way to poll a future from a synchronous thread, so I haven't figured out how to get the async code to work.

I should mention: the mystery winit bug I mentioned seems to have something to do with bevy_audio. After disabling it, I was able to get synchronous file dialogs working on both Windows and Linux. However, MacOS has the limitation that all GUI operations must be run on the main thread, which implies the same for file dialogs. So our current options seem to be either to figure out what makes async (purportedly) not break, or to figure out how to get file dialogs to not clash with audio.

@Moxinilian
Copy link
Member

Moxinilian commented Apr 28, 2021

If you can reproduce the bug with rodio alone, consider reporting it upstream at rodio's issue tracker. Otherwise, there is something to investigate in bevy_audio.

I made an example of polling a future from a system in case you want to go down that route. It's a bit scary-looking but not very complicated. You will ned the futures crate as a dependency.

use bevy::prelude::*;
use futures::pin_mut;
use futures_timer::Delay;

use std::ops::DerefMut;
use std::future::Future;
use std::time::Duration;

pub fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_startup_system(setup.system())
        .add_system(poller.system())
        .run();
}

// this defines the future we will want to poll
// it's a simple example, replace it with an actually
// useful future in your context
type FutureType = Delay;

fn setup(mut commands: Commands) {
    let future = Delay::new(Duration::from_millis(500));
    commands.spawn().insert(future);
}

fn poller(mut commands: Commands, mut query: Query<(Entity, &mut FutureType)>) {
    for (entity, mut future) in query.iter_mut() {
        println!("Frame!");
        let context = &mut futures::task::Context::from_waker(futures::task::noop_waker_ref());

        let future = future.deref_mut();
        pin_mut!(future);

        // do the polling
        if let std::task::Poll::Ready(result) = future.poll(context) {
            // Do something with the result of your future
            println!("Timer elapsed.");
            commands.entity(entity).despawn();
        }
    }
}

It's not a very efficient polling strategy as it will poll on every frame no matter what. But I hope that in your context it's not that bad.

I'd expect something to turn a future into an event or something to be a part of bevy however. That sounds like a reasonable feature request.

@PolyMeilex
Copy link

Hi! RFD dev here.
I get an impression that there is a little bit of misunderstanding here.

The only winit bug I know of related to file dialogs is: rust-windowing/winit#1779
It cheapens only on MacOs and only with sync dialogs.

The bevy_audio thing is totally unrelated to winit, my guess is that it is because cpal (dependency of rodio) initializes COM (in multi threaded mode).
It caused some issues before already, for example it is a cause of existence of this fn in winit with_drag_and_drop(bool)
rfd initializes COM too so it probably causes some conflicts. It is typical CoInitializeEx error.
I will investigate it further when I have some free time.

If you decide that bevy needs its own file dialog solution let me know, I can help with that.
Maybe as an author or rfd I'm a little bit biased, but I believe that duplicating efforts does not help anyone, I would be in favor of using rfd as a dependency of bevy implementation. I'm open to any changes that would benefit such a integration

@cart
Copy link
Member

cart commented Apr 28, 2021

@PolyMeilex thanks for the expert opinion!

It sounds like we should probably focus on fixing this issue in winit as thats our chosen "windowing abstraction" and it already has the feature and its just broken on a specific platform.

I see this as a good chance for Bevy contributors to start familiarizing themselves with winit internals. We should have some expertise in that area / know how to drive our requirements upstream.

@mockersf
Copy link
Member

Long term, we should probably have a way to display both native dialogues and custom so that they can be themed to the game

@bjorn3
Copy link
Contributor

bjorn3 commented Apr 28, 2021

Custom dialogues are impossible when running inside a sandbox like flatpak or snap unless you grant it blanket permission for all the user files. You need to use the file chooser xdg desktop portal for flatpak or snap to be able to access any file outside the sandbox.

@samcarey
Copy link

Is there a working example of using rfd with bevy?

I tried the following and it blocks on the call to poll until I close the dialog. What am I doing wrong?

use bevy::prelude::*;
use futures::pin_mut;
use rfd::AsyncFileDialog;

use std::future::Future;

fn main() {
    App::build().add_startup_system(dialog.system()).run();
}

fn dialog() {
    let future = AsyncFileDialog::new().pick_file();
    let context = &mut futures::task::Context::from_waker(futures::task::noop_waker_ref());
    pin_mut!(future);
    println!("Start Polling");
    let poll_result = future.poll(context);
    println!("Polling Result: {:?}", poll_result);
}

@samcarey
Copy link

samcarey commented Jul 27, 2021

I figured out how to use FileDialog asynchronously using the new async_compute example. I didn't see it before because I was looking at the 0.5.0 tag, which doesn't have it.

use futures_lite::future;
use std::path::PathBuf;

use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use rfd::FileDialog;

fn main() {
    App::default()
        .add_plugins(DefaultPlugins)
        .add_startup_system(dialog.system())
        .add_system(poll.system())
        .run();
}

fn dialog(mut commands: Commands, thread_pool: Res<AsyncComputeTaskPool>) {
    println!("Start Polling");
    let task = thread_pool.spawn(async move { FileDialog::new().pick_file() });
    commands.spawn().insert(task);
}

fn poll(mut commands: Commands, mut tasks: Query<(Entity, &mut Task<Option<PathBuf>>)>) {
    println!("Polling");
    for (entity, mut task) in tasks.iter_mut() {
        if let Some(result) = future::block_on(future::poll_once(&mut *task)) {
            println!("{:?}", result);
            commands.entity(entity).remove::<Task<Option<PathBuf>>>();
        }
    }
}

I tried replacing async move { FileDialog::new().pick_file() } with AsyncFileDialog::new().pick_file() and the future never returns as ready, even after I pick a file.

@RobotSnowOtter
Copy link

I was also searching for a file dialog solution. The example above that @samcarey provided was the shortest path to success I could find.

Some updates were needed to work with bevy 8.1; shared below, in case others are bumping into this. I'm no Bevy/Rust expert, so if there's a better way to do this, please, let me know.

use futures_lite::future;
use std::path::PathBuf;

use bevy::{
    prelude::*,
    tasks::{AsyncComputeTaskPool, Task},
};
use rfd::FileDialog;

fn main() {
    App::default()
        .add_plugins(DefaultPlugins)
        .add_startup_system(dialog)
        .add_system(poll)
        .run();
}

#[derive(Component)]
struct SelectedFile(Task<Option<PathBuf>>);

fn dialog(mut commands: Commands) {
    let thread_pool = AsyncComputeTaskPool::get();
    println!("Start Polling");
    let task = thread_pool.spawn(async move { FileDialog::new().pick_file() });
    commands.spawn().insert(SelectedFile(task));
}

fn poll(mut commands: Commands, mut tasks: Query<(Entity, &mut SelectedFile)>) {
    println!("Polling");
    for (entity, mut selected_file) in tasks.iter_mut() {
        if let Some(result) = future::block_on(future::poll_once(&mut selected_file.0)) {
            println!("{:?}", result);
            commands.entity(entity).remove::<SelectedFile>();
        }
    }
}

@itfanr
Copy link

itfanr commented Nov 14, 2022

But I wonder how Blender did it with a beautiful UI and cross platform File dialog?
Can we learn and make it like Blender?

@mockersf
Copy link
Member

At least on macOS, blender file UI is a pain to use and doesn't use anything from the system ui/ux.

It's painful enough that I'm using Python scripts for my most common import scenarios rather than use it...

@bjorn3
Copy link
Contributor

bjorn3 commented Nov 14, 2022

And for flatpak you have to use the org.freedesktop.portal.FileChooser dbus interface to use the native file dialog if your app doesn't have blanket permission for every file the user may want to open.

@richardhozak
Copy link
Contributor

richardhozak commented Nov 24, 2023

I've created bevy_file_dialog plugin for file dialogs using rfd and Bevy's AsyncComputeTaskPool.

I've only tested it on Linux, but it uses cross-platform apis from rfd including rfd's wrappers for wasm, so it should work everywhere.

@antoineMoPa
Copy link

I was also having issues with rfd::FileDialog, but it's possible to make it work by making sure the system where it's called runs on the main thread.

We can achieve this by passing any NonSend resource to the system (source).

Example:

fn my_system_which_asks_a_file_at_every_update(
    mut _windows: NonSend<WinitWindows>
) {
    // ...
    let files = FileDialog::new().pick_file();
    // ...
}

Quite hacky, but it seems to work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible
Projects
None yet
Development

No branches or pull requests