diff --git a/src/app/settings.rs b/src/app/settings.rs index bdfab8ff9..c66b0c1bb 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -170,6 +170,8 @@ pub struct UserSettings { /// GRIPOBJLIMIT: past this many selected objects, no grips are drawn at /// all. 0 means no limit. The drawing header carries no slot for it. pub grip_object_limit: i32, + /// Which Options page was showing when the dialog was last closed. + pub options_tab: crate::ui::window::options::OptionsTab, /// CURSORTYPE: crosshair or the platform pointer over the drawing. pub cursor_type: CursorType, /// Explicit crosshair RGB. `None` keeps automatic background contrast. @@ -325,6 +327,7 @@ impl Default for UserSettings { zoom_factor: 60, cursor_size: 5, pick_box: 3, + options_tab: crate::ui::window::options::OptionsTab::General, double_click_block_refedit: false, double_click_block_attedit: true, grip_object_limit: DEFAULT_GRIP_OBJECT_LIMIT, diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 771b2603b..bc4c020cd 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -365,6 +365,7 @@ impl OpenCADStudio { zoom_factor: self.zoom_factor, cursor_size: self.cursor_size, pick_box: self.pick_box, + options_tab: self.options_tab, double_click_block_refedit: self.double_click_block_refedit, double_click_block_attedit: self.double_click_block_attedit, grip_object_limit: self.grip_object_limit, @@ -422,6 +423,7 @@ impl OpenCADStudio { self.zoom_factor = s.zoom_factor.clamp(3, 100); self.cursor_size = s.cursor_size.clamp(1, 100); self.pick_box = s.pick_box.clamp(0, 50); + self.options_tab = s.options_tab; self.double_click_block_refedit = s.double_click_block_refedit; self.double_click_block_attedit = s.double_click_block_attedit; self.grip_object_limit = s.grip_object_limit.clamp(0, 32767); diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 9d116889e..0c27f9278 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -175,8 +175,8 @@ impl OpenCADStudio { } super::super::ModalKind::Options => sized_flow( ex, - 540, - 560, + 880, + 620, |flow| { crate::ui::window::options::view_window( &self.default_save_format, diff --git a/src/modules/manage/mod.rs b/src/modules/manage/mod.rs index f1a66e7aa..48fda655e 100644 --- a/src/modules/manage/mod.rs +++ b/src/modules/manage/mod.rs @@ -79,7 +79,13 @@ impl CadModule for ManageModule { // ── Application ─────────────────────────────────────────────────── RibbonGroup { title: "Application", - tools: vec![RibbonItem::LargeTool(about::tool())], + tools: vec![ + // The tool and its icon already existed but were never + // added to a group, so the dialog could only be reached + // by typing OPTIONS or from the start page. + RibbonItem::LargeTool(options::tool()), + RibbonItem::LargeTool(about::tool()), + ], }, ] }) diff --git a/src/modules/manage/options.rs b/src/modules/manage/options.rs index 615f31d0a..75bea9afc 100644 --- a/src/modules/manage/options.rs +++ b/src/modules/manage/options.rs @@ -1,5 +1,4 @@ use crate::modules::{IconKind, ModuleEvent, ToolDef}; -#[allow(dead_code)] pub fn tool() -> ToolDef { ToolDef { id: "OPTIONS", diff --git a/src/ui/window/options.rs b/src/ui/window/options.rs index 7f3aa32d6..86325e844 100644 --- a/src/ui/window/options.rs +++ b/src/ui/window/options.rs @@ -7,7 +7,21 @@ use iced::widget::{ use iced::{Background, Border, Element, Fill, Theme}; use std::fmt; -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +/// Width of the vertical tab rail. Wide enough for the longest translated +/// page name; German and Russian are the ones that set it. +const TAB_RAIL_WIDTH: f32 = 178.0; +/// The dialog's own size when the modal is laid out intrinsically. +const DIALOG_WIDTH: f32 = 880.0; +const DIALOG_HEIGHT: f32 = 620.0; + +/// Which page of the Options dialog is showing. +/// +/// Persisted: with nine pages, reopening on General every time means hunting +/// for the one you were last in. +#[derive( + Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, +)] +#[serde(rename_all = "kebab-case")] pub enum OptionsTab { #[default] General, @@ -841,38 +855,45 @@ pub fn view_window<'a>( OptionsTab::Drawing => drawing.into(), }; + // A vertical rail rather than a horizontal strip: the tab names are + // translated into 21 languages, and a row of them stops fitting long + // before the list of pages is complete. let tab_button = |label, tab| { let selected = active_tab == tab; - button(text(label).size(12)) + button(text(label).size(12.5)) .on_press(Message::OptionsTabChanged(tab)) - .padding([6, 14]) - .style(if selected { button::primary } else { button::secondary }) + .padding([7, 12]) + .width(Fill) + .style(if selected { button::primary } else { button::text }) }; - let tabs = row![ + let tabs = column![ tab_button(crate::t!("General"), OptionsTab::General), tab_button(crate::t!("Display"), OptionsTab::Display), tab_button(crate::t!("Selection"), OptionsTab::Selection), tab_button(crate::t!("Drawing"), OptionsTab::Drawing), ] - .spacing(6); + .spacing(2) + .width(iced::Length::Fixed(TAB_RAIL_WIDTH)); - let body = column![ - tabs, - Space::new().height(12), + let pane = column![ // Keep the scrollbar in its own lane instead of floating over the // controls at the trailing edge of the Options content. - scrollable(content).spacing(8).height(sizing.height), + scrollable(content).spacing(8).height(Fill), Space::new().height(12), - row![Space::new().width(sizing.width), close], + row![Space::new().width(Fill), close], ] - .width(sizing.width) + .width(Fill) .height(sizing.height); + let body = row![tabs, Space::new().width(18), pane] + .width(sizing.width) + .height(sizing.height); + let intrinsic = sizing.width == crate::ui::modal::ModalSizing::INTRINSIC.width; container(body) .style(container::rounded_box) .padding([16, 18]) - .width(if intrinsic { iced::Length::Fixed(540.0) } else { sizing.width }) - .height(if intrinsic { iced::Length::Fixed(560.0) } else { sizing.height }) + .width(if intrinsic { iced::Length::Fixed(DIALOG_WIDTH) } else { sizing.width }) + .height(if intrinsic { iced::Length::Fixed(DIALOG_HEIGHT) } else { sizing.height }) .into() }