File > Open starts in the last-used folder (#1889)
Open dialogs (File > Open and the single-file pickers for Place, scripts, notes and presets) now carry the most recent file's folder as initial_dir, and the desktop shell hands it to rfd's set_directory. Flips UI-217-3 to done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e8b992a6f0
commit
d4a5d974ac
|
|
@ -76,7 +76,10 @@ fn show_file_dialog(request: FileDialogRequest, parent: Option<&eframe::Frame>,
|
|||
dialog = dialog.set_parent(parent);
|
||||
}
|
||||
let answer: Pin<Box<dyn Future<Output = Option<FileDialogAnswer>> + Send>> = match request {
|
||||
FileDialogRequest::Open { multiple } => {
|
||||
FileDialogRequest::Open { multiple, initial_dir } => {
|
||||
if let Some(dir) = initial_dir {
|
||||
dialog = dialog.set_directory(dir);
|
||||
}
|
||||
let dialog = dialog.add_filter("All Formats", &open_filter_extensions(OPEN_EXTS)).add_filter("PhotoCraft", &open_filter_extensions(&["pcraft"]));
|
||||
if multiple {
|
||||
let picked = dialog.pick_files();
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ use crate::file_open::display_name;
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FileDialogRequest {
|
||||
/// Files to read: several for File › Open, one for a command that reads a file (Place,
|
||||
/// scripts, notes, presets).
|
||||
Open { multiple: bool },
|
||||
/// scripts, notes, presets). Starts in `initial_dir` (the last-used folder, UI-217-3).
|
||||
Open { multiple: bool, initial_dir: Option<String> },
|
||||
/// Where to write, starting from `suggested` (a file name, or the document's own path).
|
||||
Save { suggested: String },
|
||||
}
|
||||
|
|
@ -116,7 +116,8 @@ impl PhotocraftApp {
|
|||
/// name (the full path on the desktop) and bytes. A file that can't be read fails with
|
||||
/// "<file name>: <why>".
|
||||
pub(crate) fn pick_file_bytes(&mut self, then: impl FnOnce(&mut Self, String, Vec<u8>) -> Result<Value, String> + 'static) -> Result<Value, String> {
|
||||
self.ask_file(FileDialogRequest::Open { multiple: false }, move |app, answer| {
|
||||
let initial_dir = last_used_dir(&self.ui.recent_files);
|
||||
self.ask_file(FileDialogRequest::Open { multiple: false, initial_dir }, move |app, answer| {
|
||||
let (name, bytes) = read_picked(answer)?;
|
||||
then(app, name, bytes)
|
||||
})
|
||||
|
|
@ -124,7 +125,8 @@ impl PhotocraftApp {
|
|||
|
||||
/// File › Open: opens every chosen file, reporting each failure (see [`Self::open_paths`]).
|
||||
pub fn open_dialog_file(&mut self) -> Result<Value, String> {
|
||||
self.ask_file(FileDialogRequest::Open { multiple: true }, |app, answer| {
|
||||
let initial_dir = last_used_dir(&self.ui.recent_files);
|
||||
self.ask_file(FileDialogRequest::Open { multiple: true, initial_dir }, |app, answer| {
|
||||
match answer {
|
||||
FileDialogAnswer::Paths(paths) => {
|
||||
app.open_paths(&paths);
|
||||
|
|
@ -192,6 +194,12 @@ impl PhotocraftApp {
|
|||
}
|
||||
}
|
||||
|
||||
/// The folder an open dialog starts in: the directory of the most recent file (UI-217-3).
|
||||
fn last_used_dir(recent: &[String]) -> Option<String> {
|
||||
let dir = std::path::Path::new(recent.first()?).parent()?;
|
||||
(!dir.as_os_str().is_empty()).then(|| dir.to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
/// The name and bytes of the single file an open dialog answered with.
|
||||
fn read_picked(answer: FileDialogAnswer) -> Result<(String, Vec<u8>), String> {
|
||||
match answer {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,29 @@ fn answer(open: &Open, answer: Option<FileDialogAnswer>) {
|
|||
std::thread::spawn(move || reply.send(answer)).join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_starts_in_the_last_used_folder() {
|
||||
let (mut app, open, _) = app();
|
||||
let ctx = egui::Context::default();
|
||||
app.ui.recent_files = vec!["/pics/cat.psd".into(), "/old/dog.png".into()];
|
||||
menus::invoke(&mut app, &ctx, "file.open", json!({})).unwrap();
|
||||
app.poll_file_dialog(&ctx, None);
|
||||
assert!(
|
||||
matches!(open.borrow().as_slice(), [(FileDialogRequest::Open { multiple: true, initial_dir: Some(dir) }, _)] if dir == "/pics"),
|
||||
"{:?}",
|
||||
open.borrow().first().map(|(r, _)| r.clone())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn last_used_dir_takes_the_most_recent_parent() {
|
||||
assert_eq!(last_used_dir(&[]), None);
|
||||
assert_eq!(last_used_dir(&["/pics/cat.psd".into()]), Some("/pics".into()));
|
||||
assert_eq!(last_used_dir(&["/pics/cat.psd".into(), "/old/dog.png".into()]), Some("/pics".into()));
|
||||
assert_eq!(last_used_dir(&["cat.psd".into()]), None, "no folder to start in");
|
||||
assert_eq!(last_used_dir(&["/".into()]), None, "the root has no parent");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_app_keeps_running_while_a_dialog_is_open() {
|
||||
let (mut app, open, written) = app();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Generated by `cargo xtask scorecard` from `perf/budgets.toml`, `perf/baseline.js
|
|||
| [Performance](#performance) | 3 | 22 | 13 | 47 scenarios: 3 within budget, 22 over budget or failing, 9 tracked only, 6 not run, 7 not measurable yet (baseline 2026-10-05) |
|
||||
| [File compatibility](#file-compatibility) | 4 | 12 | 11 | io corpus: oracle ≥ 146/170, round trip ≥ 169/170; psd-tools corpus: oracle ≥ 236/309, round trip ≥ 307/309; photoshop oracles: oracle ≥ 133/256, round trip ≥ 258/256 |
|
||||
| [Tools](#tools) | 12 | 14 | 8 | |
|
||||
| [Workspace and UI](#workspace-and-ui) | 2 | 7 | 14 | settings that do nothing: 55 of 141 (target 0) |
|
||||
| [Workspace and UI](#workspace-and-ui) | 3 | 7 | 13 | settings that do nothing: 55 of 141 (target 0) |
|
||||
| [Type](#type) | 0 | 3 | 6 | |
|
||||
| [Automation](#automation) | 3 | 2 | 6 | 627 of 627 menu items live (100.0%) |
|
||||
| [Reliability](#reliability) | 5 | 0 | 2 | never-crash lints on 28 of 28 crates; panic_hunt present |
|
||||
|
|
@ -166,7 +166,7 @@ Unread: `general.color_picker`, `general.beep_when_done`, `general.export_clipbo
|
|||
|---|---|---|---|---|
|
||||
| UI-217-1 | Tool options, last Export As / Save for Web / Fill settings and font recents persist | partial | [#217](https://github.com/storytold/photocraft/issues/217) | Fill (fill_ui.rs:72) and Liquify (liquify_ui.rs:64, #418) keep their settings in Preferences (prefs.dialogs); no tool_options/export fields in Preferences; export_dialog.rs:21 png/85. Each painting tool keeps its own brush (paint_mouse.rs sync_tool_brush, #218), but those live in UiState, so they are lost at quit like the other tool options |
|
||||
| UI-217-2 | Window size and dock width remembered across restarts | done | [#217](https://github.com/storytold/photocraft/issues/217) | eframe persistence (ui.ron): persist_window for the window, egui memory for the dock panel width; window_and_panel_geometry_survive_a_restart asserts the round trip (apps/photocraft/src/main.rs; b07a2e5b, #587) |
|
||||
| UI-217-3 | File > Open starts in the last-used folder | missing | [#217](https://github.com/storytold/photocraft/issues/217) | services.rs:110 FileDialog::new() without set_directory |
|
||||
| UI-217-3 | File > Open starts in the last-used folder | done | [#217](https://github.com/storytold/photocraft/issues/217) | file_dialog.rs last_used_dir: open dialogs ask with initial_dir (the most recent file's folder) and apps services.rs passes it to rfd set_directory; open_starts_in_the_last_used_folder + last_used_dir_takes_the_most_recent_parent |
|
||||
| UI-217-4 | Preferences all take effect; recent files persist and honour recent_file_count (#204) | missing | [#217](https://github.com/storytold/photocraft/issues/217) | See the prefs audit and BUG-204-* |
|
||||
| UI-217-5 | Panel groups can float (menu + drag out of the dock) and are restored at launch | missing | [#217](https://github.com/storytold/photocraft/issues/217) | No floating groups in dock.rs/panels.rs |
|
||||
| UI-217-6 | Document tabs: overflow list, context menu (Close/Others/All/Reveal), drag to reorder | partial | [#217](https://github.com/storytold/photocraft/issues/217) | canvas.rs: Close/Others/All context menu with unsaved prompt; overflow, Reveal, drag reorder still missing |
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ note = "eframe persistence (ui.ron): persist_window for the window, egui memory
|
|||
[[item]]
|
||||
id = "UI-217-3"
|
||||
target = "File > Open starts in the last-used folder"
|
||||
status = "missing"
|
||||
status = "done"
|
||||
issue = 217
|
||||
note = "services.rs:110 FileDialog::new() without set_directory"
|
||||
note = "file_dialog.rs last_used_dir: open dialogs ask with initial_dir (the most recent file's folder) and apps services.rs passes it to rfd set_directory; open_starts_in_the_last_used_folder + last_used_dir_takes_the_most_recent_parent"
|
||||
|
||||
[[item]]
|
||||
id = "UI-217-4"
|
||||
|
|
|
|||
Loading…
Reference in New Issue