diff --git a/src/app/mod.rs b/src/app/mod.rs index a0c4a2888..cad1551e4 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2694,6 +2694,11 @@ pub enum Message { SelectionCyclingChanged(bool), /// Reveal one of the application's own folders in the system file manager. OpenFolder(String), + /// Choose another folder for fonts or plot styles (`None` result: + /// cancelled). + ChooseFolder(crate::ui::window::options::MovableFolder), + /// Use this folder, or the default one for `None`. + FolderChosen(crate::ui::window::options::MovableFolder, Option), /// Change isolines per surface in the current drawing (ISOLINES). IsolinesChanged(i16), /// The isolines slider was released; rebuild the meshes if it moved. diff --git a/src/app/settings.rs b/src/app/settings.rs index eac5429e5..2726411a4 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -443,6 +443,9 @@ pub struct UserSettings { pub file_dia: bool, /// TRIMEXTENDMODE: TRIM and EXTEND start in Quick mode (else Standard). pub trim_extend_quick: bool, + /// Fonts and plot styles kept in a chosen folder (`None`: the default). + pub fonts_folder: Option, + pub plot_styles_folder: Option, /// The file type ATTACH's picker was last left on (1-based). pub attach_filter: u32, /// SURFACEASSOCIATIVITY: new surfaces stay associative to their sources. @@ -918,6 +921,8 @@ impl Default for UserSettings { xref_fade: 50, file_dia: true, trim_extend_quick: true, + fonts_folder: None, + plot_styles_folder: None, attach_filter: 7, surface_associativity: true, ucs_ortho: true, diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 45274cb65..59d900c12 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -811,6 +811,15 @@ impl OpenCADStudio { xref_fade: crate::scene::cache::block_cache::xref_fade_ctl(), file_dia: crate::sys::file_dia(), trim_extend_quick: crate::modules::draw::modify::trim::quick_mode(), + #[cfg(not(target_arch = "wasm32"))] + fonts_folder: crate::io::font_repo::fonts_folder().map(|p| p.display().to_string()), + #[cfg(not(target_arch = "wasm32"))] + plot_styles_folder: crate::io::plot_style::plot_styles_folder() + .map(|p| p.display().to_string()), + #[cfg(target_arch = "wasm32")] + fonts_folder: None, + #[cfg(target_arch = "wasm32")] + plot_styles_folder: None, attach_filter: crate::sys::attach_filter(), surface_associativity: crate::scene::model::sweep_model::surface_associativity(), ucs_ortho: crate::modules::view::view_cmd::ucs_ortho(), @@ -948,6 +957,13 @@ impl OpenCADStudio { self.ribbon.xref_fade = crate::scene::cache::block_cache::xref_fade_ctl(); crate::sys::set_file_dia(s.file_dia); crate::modules::draw::modify::trim::set_quick_mode(s.trim_extend_quick); + #[cfg(not(target_arch = "wasm32"))] + { + crate::io::font_repo::set_fonts_folder(s.fonts_folder.as_ref().map(Into::into)); + crate::io::plot_style::set_plot_styles_folder( + s.plot_styles_folder.as_ref().map(Into::into), + ); + } crate::sys::set_attach_filter(s.attach_filter); crate::scene::model::sweep_model::set_surface_associativity(s.surface_associativity); crate::modules::view::view_cmd::set_ucs_ortho(s.ucs_ortho); diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index f625b1095..66516c997 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -9369,6 +9369,34 @@ impl OpenCADStudio { crate::sys::open_url(&path, None) } + #[cfg(target_arch = "wasm32")] + Message::ChooseFolder(_) | Message::FolderChosen(..) => Task::none(), + #[cfg(not(target_arch = "wasm32"))] + Message::ChooseFolder(folder) => Task::perform( + async { + rfd::AsyncFileDialog::new() + .pick_folder() + .await + .map(|handle| crate::sys::handle_path(&handle)) + }, + move |picked| match picked { + Some(path) => Message::FolderChosen(folder, Some(path)), + None => Message::Noop, + }, + ), + #[cfg(not(target_arch = "wasm32"))] + Message::FolderChosen(folder, path) => { + use crate::ui::window::options::MovableFolder; + match folder { + MovableFolder::Fonts => crate::io::font_repo::set_fonts_folder(path), + MovableFolder::PlotStyles => { + crate::io::plot_style::set_plot_styles_folder(path) + } + } + self.save_config(); + Task::none() + } + Message::PickDragRectToggled(rectangle) => { self.pick_drag_rect = rectangle; self.persist_settings_if_changed(); diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 007c5e4ec..e8968b928 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -375,6 +375,9 @@ impl OpenCADStudio { .map(|p| p.display().to_string()), color_books: crate::ui::color_select::book::folder() .map(|p| p.display().to_string()), + fonts_moved: crate::io::font_repo::fonts_folder().is_some(), + plot_styles_moved: crate::io::plot_style::plot_styles_folder() + .is_some(), } } #[cfg(target_arch = "wasm32")] diff --git a/src/io/font_repo.rs b/src/io/font_repo.rs index 2a798c5d7..893da920f 100644 --- a/src/io/font_repo.rs +++ b/src/io/font_repo.rs @@ -18,11 +18,26 @@ const REPO_API_URL: &str = "https://api.github.com/repos/HakanSeven12/OpenCADStu #[cfg(not(target_arch = "wasm32"))] const MAX_FONT_BYTES: u64 = 16 * 1024 * 1024; +#[cfg(not(target_arch = "wasm32"))] +static FONTS_FOLDER: std::sync::RwLock> = std::sync::RwLock::new(None); + +/// A folder chosen in Options to hold the fonts instead of the default one, +/// such as a share on the office network. +#[cfg(not(target_arch = "wasm32"))] +pub fn fonts_folder() -> Option { + FONTS_FOLDER.read().unwrap_or_else(|e| e.into_inner()).clone() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn set_fonts_folder(folder: Option) { + *FONTS_FOLDER.write().unwrap_or_else(|e| e.into_inner()) = folder; +} + /// Local store for fonts downloaded from the community repository. Text /// resolution searches here after the drawing folder. #[cfg(not(target_arch = "wasm32"))] pub fn fonts_dir() -> Option { - crate::config::config_dir().map(|dir| dir.join("fonts")) + fonts_folder().or_else(|| crate::config::config_dir().map(|dir| dir.join("fonts"))) } /// Web builds have no filesystem to cache fonts into. diff --git a/src/io/plot_style.rs b/src/io/plot_style.rs index 8b531db12..272c9fbf8 100644 --- a/src/io/plot_style.rs +++ b/src/io/plot_style.rs @@ -34,8 +34,26 @@ const STANDARD_PLOT_STYLES: &[&str] = &[ SCREENING_25_PLOT_STYLE, ]; +#[cfg(not(target_arch = "wasm32"))] +static PLOT_STYLES_FOLDER: std::sync::RwLock> = std::sync::RwLock::new(None); + +/// A folder chosen in Options to hold the plot style tables instead of the +/// default one, such as a share on the office network. +#[cfg(not(target_arch = "wasm32"))] +pub fn plot_styles_folder() -> Option { + PLOT_STYLES_FOLDER.read().unwrap_or_else(|e| e.into_inner()).clone() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn set_plot_styles_folder(folder: Option) { + *PLOT_STYLES_FOLDER.write().unwrap_or_else(|e| e.into_inner()) = folder; +} + #[cfg(not(target_arch = "wasm32"))] pub fn plot_styles_dir() -> Result { + if let Some(folder) = plot_styles_folder() { + return Ok(folder); + } crate::config::config_dir() .map(|path| path.join("plotstyles")) .ok_or_else(|| "Plot styles folder could not be resolved".to_string()) diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index 29983c08c..2913b578d 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -95,7 +95,14 @@ pub struct AppPrefs { pub page_setup_on_new_layout: bool, } -/// The fixed locations the Files page lists. +/// A Files-page location the user may move elsewhere. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MovableFolder { + Fonts, + PlotStyles, +} + +/// The locations the Files page lists. /// /// `None` where the platform could not give one — the row then says so and /// its button is disabled rather than opening nothing. @@ -110,6 +117,9 @@ pub struct Folders { pub fonts: Option, /// Where `.acb` colour books are kept and loaded ones copied to. pub color_books: Option, + /// The fonts / plot styles folder was chosen in Options. + pub fonts_moved: bool, + pub plot_styles_moved: bool, } /// Values read from the current drawing's header rather than from preferences. @@ -1750,11 +1760,13 @@ pub fn view_window<'a>( let modeling = modeling.spacing(0).width(sizing.width); - // The application has no support-file search path, so this page does not - // pretend to offer one. It shows where things actually live and opens the - // folder — which is the question people are really asking when they go - // looking for a Files page. - let folder_row = |label: std::borrow::Cow<'a, str>, path: Option| { + // The page shows where things live and opens the folder — which is the + // question people are really asking when they go looking for a Files + // page. Fonts and plot styles can move elsewhere, such as a share on the + // office network that a whole team uses. + let folder_row = |label: std::borrow::Cow<'a, str>, + path: Option, + movable: Option<(MovableFolder, bool)>| { let shown = path .clone() .unwrap_or_else(|| crate::t!("Not available").into_owned()); @@ -1764,38 +1776,68 @@ pub fn view_window<'a>( if let Some(path) = path { open = open.on_press(Message::OpenFolder(path)); } + let mut buttons = row![open].spacing(6); + if let Some((folder, moved)) = movable { + buttons = buttons.push( + button(text(crate::t!("Change...")).size(11)) + .padding([4, 10]) + .style(button::secondary) + .on_press(Message::ChooseFolder(folder)), + ); + if moved { + buttons = buttons.push( + button(text(crate::t!("Default")).size(11)) + .padding([4, 10]) + .style(button::secondary) + .on_press(Message::FolderChosen(folder, None)), + ); + } + } row![ column![text(label).size(12), text(shown).size(11),] .spacing(2) .width(Fill), - open, + buttons, ] .spacing(10) .align_y(iced::Center) }; + // Browsers have no folder picker. + let movable = |folder: MovableFolder, moved: bool| { + (!cfg!(target_arch = "wasm32")).then_some((folder, moved)) + }; let files = column![ text(crate::t!("Files")).size(15), Space::new().height(6), text(crate::t!( - "Where the application keeps its own files. These locations are fixed." + "Where the application keeps its own files." )) .size(11) .width(sizing.width), Space::new().height(16), - folder_row(crate::t!("Configuration"), folders.config.clone()), + folder_row(crate::t!("Configuration"), folders.config.clone(), None), Space::new().height(12), - folder_row(crate::t!("Plot styles"), folders.plot_styles.clone()), + folder_row( + crate::t!("Plot styles"), + folders.plot_styles.clone(), + movable(MovableFolder::PlotStyles, folders.plot_styles_moved), + ), Space::new().height(12), - folder_row(crate::t!("Plugins"), folders.plugins.clone()), + folder_row(crate::t!("Plugins"), folders.plugins.clone(), None), Space::new().height(12), - folder_row(crate::t!("Autosave files"), folders.autosave.clone()), + folder_row(crate::t!("Autosave files"), folders.autosave.clone(), None), Space::new().height(12), - folder_row(crate::t!("Fonts"), folders.fonts.clone()), + folder_row( + crate::t!("Fonts"), + folders.fonts.clone(), + movable(MovableFolder::Fonts, folders.fonts_moved), + ), Space::new().height(12), folder_row( crate::t!("Color book locations"), - folders.color_books.clone() + folders.color_books.clone(), + None, ), Space::new().height(16), text(crate::t!("Default template for new drawings")).size(12),