Tainted \\ Coders

Open a file dialog

Last updated:

To open a file dialog in Bevy you can use rfd, futures_lite and some asynchronous thread scheduling to poll for task completion.

To start we can create a startup system that spawns the dialog:

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

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

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

This will open the native operating system file dialog when our app starts. We schedule the task to run asynchronously and store the result in a component.

Then we need a system to poll this task for its completion to get the result:

use futures_lite::future;

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)
        ) {
            info!("{:?}", result);
            commands.entity(entity).remove::<SelectedFile>();
        }
    }
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, dialog)
        .add_systems(Update, poll)
        .run();
}

When the user has finally selected a file we will get back an Option<PathBuf> that we print to the console before removing the SelectedFile component from our entity.

This is of course optional and you can use the SelectedFile component however you like to read the local filesystem.

Read more