ui/dialogs/
open_project.rs

1//! Contains the code related to the "open project" dialog and form.
2use crate::dialogs::RenderableDialog;
3use egui::{Context, TextEdit, Window};
4use egui_form::garde::{GardeReport, field_path};
5use egui_form::{Form, FormField};
6use garde::Validate;
7use std::path::PathBuf;
8
9/// Defines all form data required for opening an existing project.
10#[derive(Default, Debug, Validate)]
11pub struct OpenProject {
12    /// The path where the save file is located, represented as a string.
13    #[garde(ascii, length(min = 3, max = 256), custom(path_exists))]
14    pub path: String,
15}
16
17/// Internal validator function that validates the given `value` is an existing path to a file.
18///
19/// # Errors
20/// Returns a `garde::Error` when given a path that doesn't exist or refers to a folder.
21#[allow(
22    clippy::ref_option,
23    reason = "Required by Garde's API to pass a reference"
24)]
25#[allow(
26    clippy::trivially_copy_pass_by_ref,
27    reason = "Required by Garde's API to pass a reference"
28)]
29fn path_exists(path: &String, _context: &&&()) -> garde::Result {
30    let path = PathBuf::from(path);
31
32    if !path.exists() {
33        return Err(garde::Error::new("Selected file does not exist"));
34    } else if path.is_dir() {
35        return Err(garde::Error::new(
36            "Folders are (currently) not supported. Please select a file instead.",
37        ));
38    }
39
40    Ok(())
41}
42
43impl RenderableDialog for OpenProject {
44    fn render(&mut self, context: &mut Context) -> bool {
45        let validation = self.validate();
46        let is_valid = validation.is_ok();
47        let mut form = Form::new().add_report(GardeReport::new(validation));
48
49        let mut keep_open = true;
50        // `.open` takes exclusive ownership, so we create a second flag that the buttons/UI can use.
51        let mut keep_open_inner = true;
52
53        Window::new("Open Project")
54            .open(&mut keep_open)
55            .show(context, |ui| {
56                ui.horizontal(|ui| {
57                    FormField::new(&mut form, field_path!("path"))
58                        .label("Location")
59                        .ui(ui, TextEdit::singleline(&mut self.path));
60
61                    if ui.button("…").clicked() {
62                        self.path = rfd::FileDialog::new()
63                            // TODO: add filters for extensions
64                            .pick_file()
65                            .map_or_else(String::new, |path| path.to_string_lossy().to_string());
66                    }
67                });
68
69                ui.add_enabled_ui(is_valid, |ui| {
70                    if ui.button("Open").clicked() {
71                        keep_open_inner = false;
72                    }
73                });
74            });
75
76        keep_open && keep_open_inner
77    }
78}