From 7ea1aef95e890361b593bc2829d11d5ea94e0985 Mon Sep 17 00:00:00 2001 From: Panos <25664832+mechpanos@users.noreply.github.com> Date: Thu, 10 Sep 2026 08:45:20 +0300 Subject: [PATCH] Add a Files page, and links out to the editors that own their own dialogs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The application has no support-file search path — the four folders it uses are functions returning fixed paths, not settings. Rather than build a Files page that pretends otherwise, this one answers the question people actually open it to ask: where does this thing keep my plot styles. Each row shows the location and opens it in the file manager, and a row whose path the platform could not give says so with its button disabled. General gains buttons through to the plugin manager, the keyboard shortcut editor and the alias editor. Those three have good dialogs of their own and are not absorbed; Options simply stops being a place you cannot reach them from. Co-Authored-By: Claude Opus 5 --- src/app/mod.rs | 2 + src/app/update/mod.rs | 2 + src/app/view/modal.rs | 11 +++++ src/ui/window/options.rs | 94 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+) diff --git a/src/app/mod.rs b/src/app/mod.rs index 6ee56bbe4..9269c3b3d 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2044,6 +2044,8 @@ pub enum Message { UcsIconAtOriginChanged(bool), /// Toggle selection cycling from Options; the status-bar pill toggles the same flag. SelectionCyclingChanged(bool), + /// Reveal one of the application's own folders in the system file manager. + OpenFolder(String), /// Change isolines per surface in the current drawing (ISOLINES). IsolinesChanged(i16), /// Toggle silhouette edges in the current drawing (DISPSILH). diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 3e88cb454..3b4844fe5 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -6363,6 +6363,8 @@ impl OpenCADStudio { Task::none() } + Message::OpenFolder(path) => crate::sys::open_url(&path, 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 e9b5278f6..0f59969ed 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -230,6 +230,17 @@ impl OpenCADStudio { .map_or(1, |h| h.show_solid_history), } }, + crate::ui::window::options::Folders { + config: crate::config::config_dir() + .map(|p| p.display().to_string()), + plot_styles: crate::io::plot_style::plot_styles_dir() + .ok() + .map(|p| p.display().to_string()), + plugins: crate::plugin::external::plugins_dir() + .map(|p| p.display().to_string()), + autosave: crate::config::config_dir() + .map(|_| std::env::temp_dir().display().to_string()), + }, self.double_click_block_refedit, self.double_click_block_attedit, self.cursor_type, diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index a367f24be..0923ebe21 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -25,6 +25,7 @@ const DIALOG_HEIGHT: f32 = 620.0; pub enum OptionsTab { #[default] General, + Files, OpenAndSave, Display, Drafting, @@ -74,6 +75,18 @@ pub struct AppPrefs { pub ucs_icon_at_origin: bool, } +/// The fixed 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. +#[derive(Clone, Default)] +pub struct Folders { + pub config: Option, + pub plot_styles: Option, + pub plugins: Option, + pub autosave: Option, +} + /// Values read from the current drawing's header rather than from preferences. /// /// These are saved in the DWG, so they follow the drawing rather than the @@ -142,6 +155,7 @@ pub fn view_window<'a>( prefs: AppPrefs, snap_angle_input: &'a str, drawing_prefs: DrawingPrefs, + folders: Folders, double_click_block_refedit: bool, double_click_block_attedit: bool, cursor_type: CursorType, @@ -259,6 +273,38 @@ pub fn view_window<'a>( ] .spacing(12) .align_y(iced::Center), + Space::new().height(24), + text(crate::t!("Applications")).size(15), + Space::new().height(10), + row![ + text(crate::t!("Installed plugins and their sources")).size(12).width(Fill), + button(text(crate::t!("Plugins…")).size(11)) + .on_press(Message::PluginManagerOpen) + .padding([4, 10]) + .style(button::secondary), + ] + .spacing(10) + .align_y(iced::Center), + Space::new().height(10), + row![ + text(crate::t!("Keyboard shortcuts")).size(12).width(Fill), + button(text(crate::t!("Keyboard Shortcuts…")).size(11)) + .on_press(Message::ShortcutsPanelOpen) + .padding([4, 10]) + .style(button::secondary), + ] + .spacing(10) + .align_y(iced::Center), + Space::new().height(10), + row![ + text(crate::t!("Command aliases")).size(12).width(Fill), + button(text(crate::t!("Command Aliases…")).size(11)) + .on_press(Message::AliasEditorOpen) + .padding([4, 10]) + .style(button::secondary), + ] + .spacing(10) + .align_y(iced::Center), ] .spacing(0) .width(sizing.width); @@ -1464,10 +1510,57 @@ 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| { + let shown = path.clone().unwrap_or_else(|| crate::t!("Not available").into_owned()); + let mut open = button(text(crate::t!("Open folder")).size(11)) + .padding([4, 10]) + .style(button::secondary); + if let Some(path) = path { + open = open.on_press(Message::OpenFolder(path)); + } + row![ + column![ + text(label).size(12), + text(shown).size(11), + ] + .spacing(2) + .width(Fill), + open, + ] + .spacing(10) + .align_y(iced::Center) + }; + + 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." + )) + .size(11) + .width(sizing.width), + Space::new().height(16), + folder_row(crate::t!("Configuration"), folders.config.clone()), + Space::new().height(12), + folder_row(crate::t!("Plot styles"), folders.plot_styles.clone()), + Space::new().height(12), + folder_row(crate::t!("Plugins"), folders.plugins.clone()), + Space::new().height(12), + folder_row(crate::t!("Autosave files"), folders.autosave.clone()), + ] + .spacing(0) + .width(sizing.width); + let content: Element<'a, Message> = match active_tab { OptionsTab::General => general.into(), OptionsTab::Display => display_element.into(), OptionsTab::Selection => selection.into(), + OptionsTab::Files => files.into(), OptionsTab::OpenAndSave => open_and_save.into(), OptionsTab::Drafting => drafting.into(), OptionsTab::Modeling => modeling.into(), @@ -1488,6 +1581,7 @@ pub fn view_window<'a>( }; let tabs = column![ tab_button(crate::t!("General"), OptionsTab::General), + tab_button(crate::t!("Files"), OptionsTab::Files), tab_button(crate::t!("Open and Save"), OptionsTab::OpenAndSave), tab_button(crate::t!("Display"), OptionsTab::Display), tab_button(crate::t!("Drafting"), OptionsTab::Drafting),