graphic design tool
git clone https://git.lucas.co/cce-designer.git
fix: file choosers open at the loaded project's directory
Save As / Open spawned cce-files with no path, so the chooser landed in its
remembered last-visited directory — wherever the file manager was last used
(the actual remembered value was cce-icons/svg), not where the current
project lives. Both choosers now pass the loaded project's parent directory
positionally, which cce-files already honors; a scratch project passes
nothing and keeps the remembered fallback. Verified live: with a project
loaded from ~/designs/gears, Save As opens showing ~/designs with the
project selected and the name box prefilled.
Passing a start dir also skips the chooser's remembered-dir restore step
entirely. The reported open latency did not reproduce headless — click-to-
mapped measured 0.12s, and 0.35s cold with the real remembered directory —
so no speculative fix beyond that; if it persists it is environmental
(compositor flight, cold caches) rather than in either app's startup path.
src/app.rs | 16 ++++++++++++++++
src/main.rs | 15 +++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/src/app.rs b/src/app.rs
index f2b0436..ffaf3b5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1747,8 +1747,21 @@ impl State {
names
}
+ /// Where a file chooser should open: the loaded project's parent directory
+ /// (the current working context — sibling projects live there), or None to
+ /// let cce-files use its remembered location. Passing it also skips the
+ /// chooser's remembered-dir restore outright.
+ pub(crate) fn chooser_start_dir(&self) -> Option<std::path::PathBuf> {
+ self.loaded_project_path
+ .as_ref()
+ .and_then(|p| p.parent())
+ .filter(|d| d.is_dir())
+ .map(|d| d.to_path_buf())
+ }
+
pub fn open_file_chooser(&self) {
let Some(sender) = self.event_sender.clone() else { return };
+ let start_dir = self.chooser_start_dir();
std::thread::spawn(move || {
use std::process::{Command, Stdio};
@@ -1786,6 +1799,7 @@ impl State {
let child = match Command::new(exe_path)
.arg("--select")
+ .args(start_dir.as_deref())
.stdout(Stdio::piped())
.spawn()
{
@@ -1815,6 +1829,7 @@ impl State {
pub fn save_file_chooser(&self) {
let Some(sender) = self.event_sender.clone() else { return };
+ let start_dir = self.chooser_start_dir();
std::thread::spawn(move || {
use std::process::{Command, Stdio};
@@ -1852,6 +1867,7 @@ impl State {
let child = match Command::new(exe_path)
.arg("--save")
+ .args(start_dir.as_deref())
.stdout(Stdio::piped())
.spawn()
{
diff --git a/src/main.rs b/src/main.rs
index dde00da..6c4a921 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -69,6 +69,21 @@ mod tests {
use crate::shortcut::{Shortcut, ShortcutManager, Action};
use crate::geometry::{GAttribute, GVertex, Geometry, line_vertices};
+ /// The choosers open in the loaded project's parent — the "current view" —
+ /// and fall back to cce-files' remembered location only when nothing is
+ /// loaded (a scratch project has no place to point at).
+ #[test]
+ fn test_chooser_opens_at_the_loaded_projects_parent() {
+ let mut state = State::new(false);
+ assert_eq!(state.chooser_start_dir(), None, "scratch project must not pin a dir");
+
+ let dir = std::env::temp_dir().join("cce-designer-test-projects").join("gears");
+ std::fs::create_dir_all(&dir).unwrap();
+ state.loaded_project_path = Some(dir.clone());
+ assert_eq!(state.chooser_start_dir().as_deref(), dir.parent(),
+ "chooser must start where the current project lives");
+ }
+
/// The default-project pointer must survive the KDL round trip state.kdl
/// actually goes through — serde alone passing means nothing if
/// json_to_kdl_string / parse_kdl_to_json drop or retype the field.