Implement export of the profile [ci skip]

This commit is contained in:
biroder
2026-01-13 17:58:11 +02:00
parent 4570a2401c
commit da1d60adce
11 changed files with 1548 additions and 486 deletions
Generated
+881 -2
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -82,6 +82,7 @@ pelite = { version = ">=0.9.2", default-features = false, features = ["std"] }
sdl2 = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "244ae85833cff4f97ab4b58331741be20e422bd7", optional = true, features = ["unsafe_textures", "bundled", "static-link"] }
sdl2-sys = { git = "https://github.com/doukutsu-rs/rust-sdl2.git", rev = "244ae85833cff4f97ab4b58331741be20e422bd7", optional = true, features = ["bundled", "static-link"] }
rc-box = "1.2.0"
rfd = "0.15.4"
serde = { version = "1", features = ["derive"] }
serde_derive = "1"
serde_cbor = { version = "0.11", optional = true }
+24 -2
View File
@@ -8,7 +8,8 @@
"yes": "Yes",
"no": "No",
"on": "ON",
"off": "OFF"
"off": "OFF",
"choose_file": "No file selected…"
},
"menus": {
"main_menu": {
@@ -67,7 +68,28 @@
"save_manage_menu": {
"import_export_save": "Import/Export Save...",
"import_format": "Import format",
"export_format": "Export format"
"export_format": "Export format",
"action_type": {
"entry": "Action:",
"import": "Import",
"export": "Export"
},
"save_location": {
"import": "Import location",
"export": "Export location"
},
"save_format": {
"entry": "Save format:",
"freeware": "Freeware",
"plus": "Cave Story+ (PC)",
"switch": "Cave Story+ (Switch)",
"auto": "Auto-detect"
},
"file_filters": {
"freeware": "Cave Story save",
"plus": "Cave Story+ save",
"switch": "Cave Story+ save"
}
},
"options_menu": {
"graphics": "Graphics...",
+203 -436
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
@@ -17,7 +17,7 @@ use crate::framework::{filesystem, graphics};
use crate::game::caret::{Caret, CaretType};
use crate::game::npc::NPCTable;
use crate::game::player::TargetPlayer;
use crate::game::profile::{GameProfile, SaveContainer, SaveFormat, SaveSlot};
use crate::game::profile::{GameProfile, SaveContainer, SaveFormat, SaveParams, SaveSlot};
use crate::game::scripting::tsc::credit_script::{CreditScript, CreditScriptVM};
use crate::game::scripting::tsc::text_script::{
ScriptMode, TextScript, TextScriptEncoding, TextScriptExecutionState, TextScriptVM,
@@ -697,9 +697,7 @@ impl SharedGameState {
let mut save_container = SaveContainer::load(ctx, self)?;
save_container.set_profile(slot, profile);
//save_container.write_save(ctx, state, SaveFormat::Generic, None, None);
//save_container.write_save(ctx, state, self.settings.save_format, None, None);
save_container.save(ctx, self)?;
save_container.save(ctx, self, SaveParams::default())?;
} else {
log::info!("Mod has saves disabled.");
}
-2
View File
@@ -183,8 +183,6 @@ impl Weapon {
self.refire_timer = 4;
}
// todo lua hook
match self.wtype {
WeaponType::None => {}
WeaponType::Snake => self.tick_snake(player, player_id, bullet_manager, state),
+5 -1
View File
@@ -86,8 +86,12 @@ impl Locale {
}
}
pub fn ts(&self, key: &str) -> String {
self.t(key).to_owned()
}
pub fn tt(&self, key: &str, args: &[(&str, &str)]) -> String {
let mut string = self.t(key).to_owned();
let mut string = self.ts(key);
for (key, value) in args.iter() {
string = string.replace(&format!("{{{}}}", key), &value);
+107 -12
View File
@@ -1,4 +1,6 @@
use std::cell::Cell;
use std::collections::HashMap;
use std::path::PathBuf;
use crate::common::{Color, Rect};
use crate::components::draw_common::{draw_number, Alignment};
@@ -9,6 +11,7 @@ use crate::game::shared_game_state::{GameDifficulty, MenuCharacter, SharedGameSt
use crate::graphics::font::Font;
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::menu::save_select_menu::MenuSaveInfo;
use crate::util::file_picker::{open_file_picker, FilePickerParams};
pub mod controls_menu;
pub mod coop_menu;
@@ -17,6 +20,7 @@ pub mod save_select_menu;
pub mod settings_menu;
const MENU_MIN_PADDING: f32 = 30.0;
const MENU_DISABLED_COLOR: (u8, u8, u8, u8) = (0xa0, 0xa0, 0xff, 0xff);
#[derive(Clone, Debug)]
pub enum ControlMenuData {
@@ -43,6 +47,7 @@ pub enum MenuEntry {
PlayerSkin,
Control(String, ControlMenuData),
Spacer(f64),
FilePicker(String, bool, FilePickerParams, Option<Vec<PathBuf>>), // text, display selected file names, params, selected files/dirs
}
impl MenuEntry {
@@ -64,6 +69,7 @@ impl MenuEntry {
MenuEntry::PlayerSkin => 24.0,
MenuEntry::Control(_, _) => 16.0,
MenuEntry::Spacer(height) => *height,
MenuEntry::FilePicker(_, _, _, _) => 16.0,
}
}
@@ -85,6 +91,7 @@ impl MenuEntry {
MenuEntry::PlayerSkin => true,
MenuEntry::Control(_, _) => true,
MenuEntry::Spacer(_) => false,
MenuEntry::FilePicker(_, _, _, _) => true,
}
}
}
@@ -97,6 +104,7 @@ pub enum MenuSelectionResult<'a, T: std::cmp::PartialEq> {
Right(T, &'a mut MenuEntry, i16),
}
#[derive(Clone)]
pub struct Menu<T: std::cmp::PartialEq> {
pub x: isize,
pub y: isize,
@@ -154,6 +162,26 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
}
}
pub fn get_entry(&self, id: T) -> Option<&MenuEntry> {
for i in 0..self.entries.len() {
if self.entries[i].0 == id {
return Some(&self.entries[i].1);
}
}
None
}
pub fn get_entry_mut(&mut self, id: T) -> Option<&mut MenuEntry> {
for i in 0..self.entries.len() {
if self.entries[i].0 == id {
return Some(&mut self.entries[i].1);
}
}
None
}
pub fn update_width(&mut self, state: &SharedGameState) {
let mut width = self.width as f32;
@@ -216,7 +244,25 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
MenuEntry::NewSave => {}
MenuEntry::PlayerSkin => {}
MenuEntry::Control(_, _) => {}
MenuEntry::Spacer(_) => {}
MenuEntry::Spacer(_) => {},
MenuEntry::FilePicker(entry, display, _, selection) => {
let mut entry_with_selection = entry.clone();
if *display {
entry_with_selection.push_str(" ");
let filename = selection.as_ref()
.and_then(|files| files.first())
.and_then(|file| file.file_name())
.and_then(|filename| filename.to_str())
.unwrap_or(state.loc.t("common.choose_file"));
entry_with_selection.push_str(filename);
}
let entry_width = state.font.builder().compute_width(&entry_with_selection) + 32.0;
width = width.max(entry_width);
},
}
}
@@ -353,21 +399,22 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
batch.draw(ctx)?;
let options_x = if self.center_options {
let mut longest_option_width = 20.0;
let mut longest_entry_width = 20.0;
for (_, entry) in &self.entries {
match entry {
let text_width = match entry {
MenuEntry::Options(text, _, _) | MenuEntry::Active(text) => {
let text_width = state.font.builder().compute_width(text) + 32.0;
if text_width > longest_option_width {
longest_option_width = text_width;
}
state.font.builder().compute_width(text) + 32.0
}
_ => {}
_ => 0.0
};
if text_width > longest_entry_width {
longest_entry_width = text_width;
}
}
(state.canvas_size.0 / 2.0) - (longest_option_width / 2.0)
(state.canvas_size.0 / 2.0) - (longest_entry_width / 2.0)
} else {
self.x as f32
};
@@ -471,7 +518,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
let mut builder = state.font.builder().position(x, local_y);
if !*is_white {
builder = builder.color((0xa0, 0xa0, 0xff, 0xff));
builder = builder.color(MENU_DISABLED_COLOR);
}
builder.draw(&line, ctx, &state.constants, &mut state.texture_set)?;
@@ -482,7 +529,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
y += entry.height() as f32 * (lines.len() - 1) as f32;
}
MenuEntry::Disabled(name) => {
state.font.builder().position(self.x as f32 + 20.0, y).color((0xa0, 0xa0, 0xff, 0xff)).draw(
state.font.builder().position(self.x as f32 + 20.0, y).color(MENU_DISABLED_COLOR).draw(
name,
ctx,
&state.constants,
@@ -548,7 +595,7 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
.font
.builder()
.position(self.x as f32 + 20.0, y + 16.0)
.color((0xc0, 0xc0, 0xff, 0xff))
.color(MENU_DISABLED_COLOR)
.draw(description_text, ctx, &state.constants, &mut state.texture_set)?;
}
MenuEntry::OptionsBar(name, percent) => {
@@ -739,6 +786,42 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
}
}
}
MenuEntry::FilePicker(name, display, _, selection) => {
// TODO: fix text is rendered out of the text box bounds if the filename is too long.
let name_text_len = state.font.builder().compute_width(name);
let value_text = selection.as_ref()
.and_then(|files| files.first())
.and_then(|file| file.file_name())
.and_then(|filename| filename.to_str())
.unwrap_or(state.loc.t("common.choose_file"));
// Draw the entry as disabled on unsupported platforms
let text_color = if cfg!(not(any(target_os = "android", target_os = "horizon"))) {
state.font.builder().get_color()
} else {
MENU_DISABLED_COLOR
};
state.font.builder().position(self.x as f32 + 20.0, y)
.draw(
name,
ctx,
&state.constants,
&mut state.texture_set,
)?;
if *display {
state.font.builder()
.position(self.x as f32 + 25.0 + name_text_len, y)
.draw(
value_text,
ctx,
&state.constants,
&mut state.texture_set,
)?;
}
}
_ => {}
}
@@ -882,6 +965,18 @@ impl<T: std::cmp::PartialEq + std::default::Default + Clone> Menu<T> {
return MenuSelectionResult::Selected(idx, entry);
}
}
MenuEntry::FilePicker(_, _, params, selection) => {
if self.selected == idx && controller.trigger_ok()
|| state.touch_controls.consume_click_in(entry_bounds)
{
state.sound_manager.play_sfx(18);
self.selected = idx.clone();
*selection = open_file_picker(params);
return MenuSelectionResult::Selected(idx, entry);
}
}
_ => {}
}
}
+238 -27
View File
@@ -1,12 +1,15 @@
use pelite::pe::imports::Import;
use crate::framework::context::Context;
use crate::framework::error::GameResult;
use crate::framework::filesystem;
use crate::game::profile::{GameProfile, SaveContainer, SaveFormat, SaveSlot};
use crate::game::profile::{GameProfile, SaveContainer, SaveFormat, SaveParams, SaveSlot};
use crate::game::shared_game_state::{GameDifficulty, SharedGameState};
use crate::input::combined_menu_controller::CombinedMenuController;
use crate::menu::coop_menu::PlayerCountMenu;
use crate::menu::MenuEntry;
use crate::menu::{Menu, MenuSelectionResult};
use crate::util::file_picker::FilePickerParams;
#[derive(Clone, Copy)]
pub struct MenuSaveInfo {
@@ -91,19 +94,141 @@ impl Default for LoadConfirmMenuEntry {
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum ImportExportMenuEntry {
Format(SaveFormat),
ExportLocation,
Format,
Import,
Export,
Back,
}
#[derive(Clone, Copy)]
enum ImportExportLocation {
Filesystem,
// TODO: add some Switch emulators
}
impl Default for ImportExportLocation {
fn default() -> Self {
Self::Filesystem
}
}
#[derive(Clone)]
pub struct MenuExportInfo {
pub location: ImportExportLocation,
pub format: SaveFormat,
pub picker_params: FilePickerParams,
pub save_params: SaveParams,
}
impl Default for MenuExportInfo {
fn default() -> Self {
Self {
location: ImportExportLocation::Filesystem,
format: SaveFormat::Freeware,
picker_params: FilePickerParams::new(),
save_params: SaveParams::default()
}
}
}
impl MenuExportInfo {
pub fn new() -> Self {
Self::default()
}
pub fn set_picker_params(&mut self, state: &SharedGameState, is_export: bool) {
let filename = SaveContainer::get_save_filename(&self.format, None).split_off(1);
let mut params = match self.format {
SaveFormat::Freeware => {
FilePickerParams::new()
.pick_dirs(true)
}
SaveFormat::Plus | SaveFormat::Switch => {
FilePickerParams::new()
.file_name(Some(filename))
.filter(state.loc.ts("menus.save_manage_menu.file_filters.plus"), vec![
"dat".to_owned()
])
}
SaveFormat::Generic => {
FilePickerParams::new()
.file_name(Some(filename))
.filter(state.loc.ts("menus.save_manage_menu.file_filters.generic"), vec![
"json".to_owned()
])
}
};
if is_export {
params = params.save(true);
};
if let Some(fs_container) = &state.fs_container {
params = params.starting_dir(Some(fs_container.user_path.clone()));
}
self.picker_params = params;
}
}
impl ImportExportMenuEntry {
fn format_from_value(val: usize) -> Option<SaveFormat> {
match val {
0 => None, // Auto
1 => Some(SaveFormat::Freeware),
2 => Some(SaveFormat::Plus),
3 => Some(SaveFormat::Switch),
_ => unreachable!()
}
}
fn fpicker_from_format(state: &SharedGameState, format: SaveFormat, is_export: bool) -> FilePickerParams {
let filename = SaveContainer::get_save_filename(&format, None).split_off(1);
let mut params = match format {
SaveFormat::Freeware => {
FilePickerParams::new()
.pick_dirs(true)
}
SaveFormat::Plus | SaveFormat::Switch => {
FilePickerParams::new()
.file_name(Some(filename))
.filter(state.loc.ts("menus.save_manage_menu.file_filters.plus"), vec![
"dat".to_owned()
])
}
SaveFormat::Generic => {
FilePickerParams::new()
.file_name(Some(filename))
.filter(state.loc.ts("menus.save_manage_menu.file_filters.generic"), vec![
"json".to_owned()
])
}
};
if is_export && format != SaveFormat::Freeware {
params = params.save(true);
};
if let Some(fs_container) = &state.fs_container {
params = params.starting_dir(Some(fs_container.user_path.clone()));
}
params
}
}
impl Default for ImportExportMenuEntry {
fn default() -> Self {
ImportExportMenuEntry::Format(SaveFormat::Freeware)
ImportExportMenuEntry::Format
}
}
pub struct SaveSelectMenu {
pub saves: [MenuSaveInfo; 3],
pub export_info: MenuExportInfo,
current_menu: CurrentMenu,
save_menu: Menu<SaveMenuEntry>,
save_detailed: Menu<usize>,
@@ -119,6 +244,7 @@ impl SaveSelectMenu {
pub fn new() -> SaveSelectMenu {
SaveSelectMenu {
saves: [MenuSaveInfo::default(); 3],
export_info: MenuExportInfo::default(),
current_menu: CurrentMenu::SaveMenu,
save_menu: Menu::new(0, 0, 230, 0),
coop_menu: PlayerCountMenu::new(),
@@ -139,6 +265,7 @@ impl SaveSelectMenu {
self.difficulty_menu = Menu::new(0, 0, 130, 0);
self.delete_confirm = Menu::new(0, 0, 75, 0);
self.load_confirm = Menu::new(0, 0, 75, 0);
self.import_export_menu = Menu::new(0, 0, 75, 0);
self.skip_difficulty_menu = false;
let mut should_mutate_selection = true;
@@ -147,7 +274,7 @@ impl SaveSelectMenu {
for (iter, save) in self.saves.iter_mut().enumerate() {
if let Some(slot) = state.get_save_slot(iter + 1) {
if let Some(loaded_profile) = save_container.get_profile(slot) {
log::debug!("Loading save select menu. Iter - {}. {}", iter, loaded_profile.is_empty());
log::trace!("Loading save select menu. Iter - {}. {}", iter, loaded_profile.is_empty());
save.current_map = loaded_profile.current_map;
save.max_life = loaded_profile.max_life;
save.life = loaded_profile.life;
@@ -179,7 +306,7 @@ impl SaveSelectMenu {
}
}
self.save_menu.push_entry(SaveMenuEntry::ImportExport, MenuEntry::Disabled(state.loc.t("menus.save_manage_menu.import_export_save").to_owned()));
self.save_menu.push_entry(SaveMenuEntry::ImportExport, MenuEntry::Active(state.loc.ts("menus.save_manage_menu.import_export_save")));
self.save_menu.push_entry(SaveMenuEntry::Back, MenuEntry::Active(state.loc.t("common.back").to_owned()));
self.difficulty_menu.push_entry(
@@ -203,8 +330,6 @@ impl SaveSelectMenu {
self.difficulty_menu.selected = DifficultyMenuEntry::Difficulty(GameDifficulty::Normal);
//self.coop_menu.init(state, ctx);
self.delete_confirm.push_entry(
DeleteConfirmMenuEntry::Title,
MenuEntry::Disabled(state.loc.t("menus.save_menu.delete_confirm").to_owned()),
@@ -233,6 +358,37 @@ impl SaveSelectMenu {
self.save_detailed.push_entry(0, MenuEntry::SaveDataSingle(save));
}
self.export_info.format = state.settings.save_format;
//self.export_info.set_picker_params(state, true);
self.import_export_menu.push_entry(
ImportExportMenuEntry::Format,
MenuEntry::Options(
state.loc.ts("menus.save_manage_menu.save_format.entry"),
0,
vec![
state.loc.ts("menus.save_manage_menu.save_format.auto"),
state.loc.ts("menus.save_manage_menu.save_format.freeware"),
state.loc.ts("menus.save_manage_menu.save_format.plus"),
state.loc.ts("menus.save_manage_menu.save_format.switch"),
]
)
);
self.import_export_menu.push_entry(
ImportExportMenuEntry::Import,
MenuEntry::Disabled(state.loc.ts("menus.save_manage_menu.action_type.import"))
);
self.import_export_menu.push_entry(
ImportExportMenuEntry::Export,
MenuEntry::FilePicker(
state.loc.ts("menus.save_manage_menu.action_type.export"),
false,
ImportExportMenuEntry::fpicker_from_format(state, self.export_info.format, true),
None
)
);
self.import_export_menu.push_entry(ImportExportMenuEntry::Back, MenuEntry::Active(state.loc.ts("common.back")));
self.update_sizes(state);
Ok(())
@@ -268,6 +424,11 @@ impl SaveSelectMenu {
self.save_detailed.update_height(state);
self.save_detailed.x = ((state.canvas_size.0 - self.save_detailed.width as f32) / 2.0).floor() as isize;
self.save_detailed.y = -40 + ((state.canvas_size.1 - self.save_detailed.height as f32) / 2.0).floor() as isize;
self.import_export_menu.update_width(state);
self.import_export_menu.update_height(state);
self.import_export_menu.x = ((state.canvas_size.0 - self.import_export_menu.width as f32) / 2.0).floor() as isize;
self.import_export_menu.y = ((state.canvas_size.1 - self.import_export_menu.height as f32) / 2.0).floor() as isize;
}
pub fn tick(
@@ -301,19 +462,10 @@ impl SaveSelectMenu {
self.current_menu = CurrentMenu::LoadConfirm;
self.load_confirm.selected = LoadConfirmMenuEntry::Start;
/*
if let Ok(_) =
filesystem::user_open(ctx, state.get_save_filename(state.save_slot).unwrap_or(String::new()))
{
if let (_, MenuEntry::SaveData(save)) = self.save_menu.entries[slot] {
self.save_detailed.entries.clear();
self.save_detailed.push_entry(0, MenuEntry::SaveDataSingle(save));
}
self.current_menu = CurrentMenu::LoadConfirm;
self.load_confirm.selected = LoadConfirmMenuEntry::Start;
}
*/
}
MenuSelectionResult::Selected(SaveMenuEntry::ImportExport, _) => {
self.current_menu = CurrentMenu::ImportExport;
self.import_export_menu.selected = ImportExportMenuEntry::Back;
}
_ => (),
},
@@ -344,12 +496,9 @@ impl SaveSelectMenu {
match self.save_menu.selected {
SaveMenuEntry::Load(slot) => {
state.sound_manager.play_sfx(17); // Player Death sfx
//filesystem::user_delete(ctx, state.get_save_filename(slot + 1).unwrap_or(String::new()))?;
let mut save = SaveContainer::load(ctx, state)?;
//let mod_id = state.get_save_mod_id();
save.delete_profile(&ctx, state.get_save_slot(slot + 1).unwrap());
//save.write_save(ctx, state, SaveFormat::Generic, None, None);
save.save(ctx, state);
save.save(ctx, state, SaveParams::default())?;
}
_ => (),
}
@@ -381,7 +530,67 @@ impl SaveSelectMenu {
}
_ => (),
},
CurrentMenu::ImportExport => todo!(),
CurrentMenu::ImportExport => match self.import_export_menu.tick(controller, state) {
MenuSelectionResult::Selected(ImportExportMenuEntry::Format, toggle)
| MenuSelectionResult::Right(ImportExportMenuEntry::Format, toggle, _) => {
if let MenuEntry::Options(_, value, _) = toggle {
*value = match *value {
0..3 => *value + 1,
3 => 0,
_ => unreachable!(),
};
let format = ImportExportMenuEntry::format_from_value(*value).unwrap_or(state.settings.save_format);
self.export_info.format = format;
self.export_info.picker_params = ImportExportMenuEntry::fpicker_from_format(state, format, true);
let export_file_picker = self.import_export_menu.get_entry_mut(ImportExportMenuEntry::Export).unwrap();
if let MenuEntry::FilePicker(_, _, params, _) = export_file_picker {
*params = self.export_info.picker_params.clone();
}
}
}
MenuSelectionResult::Left(ImportExportMenuEntry::Format, toggle, _) => {
if let MenuEntry::Options(_, value, _) = toggle {
*value = match *value {
1..=3 => *value - 1,
0 => 3,
_ => unreachable!(),
};
let format = ImportExportMenuEntry::format_from_value(*value).unwrap_or(state.settings.save_format);
self.export_info.format = format;
self.export_info.picker_params = ImportExportMenuEntry::fpicker_from_format(state, format, true);
let export_file_picker = self.import_export_menu.get_entry_mut(ImportExportMenuEntry::Export).unwrap();
if let MenuEntry::FilePicker(_, _, params, _) = export_file_picker {
*params = self.export_info.picker_params.clone();
}
}
}
MenuSelectionResult::Selected(ImportExportMenuEntry::Export, entry) => {
let out_path = if let MenuEntry::FilePicker(_, _, _, selection) = entry {
if let Some(location) = selection {
location.first()
} else {
None
}
} else { None };
if out_path.is_none() {
// Export path is not selected, so we break export operation
return Ok(());
}
let mut save_container = SaveContainer::load(ctx, state)?;
save_container.export(state, ctx, self.export_info.format, self.export_info.save_params.clone(), out_path.unwrap().clone())?;
}
MenuSelectionResult::Selected(ImportExportMenuEntry::Back, _) | MenuSelectionResult::Canceled => {
self.current_menu = CurrentMenu::SaveMenu;
}
_ => (),
},
}
Ok(())
@@ -406,7 +615,9 @@ impl SaveSelectMenu {
self.save_detailed.draw(state, ctx)?;
self.load_confirm.draw(state, ctx)?;
}
CurrentMenu::ImportExport => todo!()
CurrentMenu::ImportExport => {
self.import_export_menu.draw(state, ctx)?;
}
}
Ok(())
}
+86
View File
@@ -0,0 +1,86 @@
use std::collections::HashMap;
use std::path::PathBuf;
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
use rfd::FileDialog;
#[derive(Clone, Debug, Default)]
pub struct FilePickerParams {
pub save: bool, /// Opens save file dialog
pub multiple: bool,
pub pick_dirs: bool,
pub file_name: Option<String>, /// Starting file name.
pub starting_dir: Option<PathBuf>,
pub filters: HashMap<String, Vec<String>>, // filter name -> file extensions
}
impl FilePickerParams {
pub fn new() -> Self {
Self::default()
}
pub fn save(mut self, save: bool) -> Self {
self.save = save;
self
}
pub fn multiple(mut self, multiple: bool) -> Self {
self.multiple = multiple;
self
}
pub fn pick_dirs(mut self, pick_dirs: bool) -> Self {
self.pick_dirs = pick_dirs;
self
}
pub fn file_name(mut self, file_name: Option<String>) -> Self {
self.file_name = file_name;
self
}
pub fn starting_dir(mut self, starting_dir: Option<PathBuf>) -> Self {
self.starting_dir = starting_dir;
self
}
pub fn filter(mut self, name: String, ext: Vec<String>) -> Self {
self.filters.insert(name, ext);
self
}
}
#[cfg(not(any(target_os = "android", target_os = "horizon")))]
pub fn open_file_picker(params: &FilePickerParams) -> Option<Vec<PathBuf>> {
log::trace!("Call a file picker dialog with params: {:?}", params.clone());
let mut dialog = FileDialog::new();
if let Some(filename) = params.file_name.clone() {
dialog = dialog.set_file_name(filename);
}
if let Some(dir) = params.starting_dir.clone() {
dialog = dialog.set_directory(dir);
}
for filter in params.filters.iter() {
dialog = dialog.add_filter(filter.0, filter.1);
}
let selected_files = match (params.pick_dirs, params.multiple, params.save) {
(_, _, true) => dialog.save_file().map(|path| vec![path]),
(true, false, _) => dialog.pick_folder().map(|path| vec![path]),
(true, true, _) => dialog.pick_folders(),
(false, false, _) => dialog.pick_file().map(|path| vec![path]),
(false, true, _) => dialog.pick_files(),
};
log::trace!("Selected file entries: {:?}", selected_files.clone());
return selected_files;
}
#[cfg(any(target_os = "android", target_os = "horizon"))]
pub fn open_file_picker(params: &FileChooserParams) -> Option<Vec<PathBuf>> {
None
}
+1
View File
@@ -1,3 +1,4 @@
pub mod bitvec;
pub mod browser;
pub mod file_picker;
pub mod rng;