diff --git a/src/app/mod.rs b/src/app/mod.rs index fe15f1ebb..057ddd875 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -387,6 +387,10 @@ pub(super) struct OpenCADStudio { /// though the three factors are currently equal — the user unchecked the /// "Uniform scale" box for them (#427). Keyed by entity handle. props_asym_scale: std::collections::HashSet, + /// Collapsed Properties-panel section titles. This belongs to the app, + /// rather than an individual document tab, so the same view preference is + /// used by every currently open drawing/project. + collapsed_property_sections: rustc_hash::FxHashSet, /// Which Start-page section is shown when the page is too narrow for all /// three side by side and falls back to a tab bar. start_section: StartSection, @@ -2558,6 +2562,8 @@ pub enum Message { /// Toggle a collapsed coordinate group ("Position", "Scale", …) open or /// closed in the Properties panel, keyed `section:base`. PropGroupToggle(String), + /// Toggle an entire Properties-panel section, keyed by its title. + PropSectionToggle(String), /// Toggle the editable-dropdown (block Name) option list open/closed. PropEditChoiceToggle, /// User is typing in a block-attribute value field (live buffer update), @@ -3314,6 +3320,7 @@ impl OpenCADStudio { discussions: Vec::new(), discussions_loading: false, props_asym_scale: std::collections::HashSet::new(), + collapsed_property_sections: rustc_hash::FxHashSet::default(), start_section: StartSection::default(), start_action_w: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)), history_content: iced::widget::text_editor::Content::new(), diff --git a/src/app/properties.rs b/src/app/properties.rs index a72daeaf4..d203b9f51 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -47,6 +47,10 @@ impl OpenCADStudio { // Expanded coordinate groups persist across rebuilds AND selection // changes — it's a per-user view preference, not per-entity state. let expanded_groups = std::mem::take(&mut self.tabs[i].properties.expanded_groups); + // Section state belongs to the app, not a drawing tab, so the user's + // collapsed sections carry across every currently open drawing/project. + // The empty app-level default keeps a first-run palette fully expanded. + let collapsed_sections = self.collapsed_property_sections.clone(); // Which entities the previous panel was built for — an uncommitted // edit buffer only survives a rebuild for the *same* selection. let prev_handles = std::mem::take(&mut self.tabs[i].properties.source_handles); @@ -2278,6 +2282,7 @@ impl OpenCADStudio { None }; panel.expanded_groups = expanded_groups; + panel.collapsed_sections = collapsed_sections; panel.source_handles = new_handles; panel.prop_vertex = prop_vertex; panel.prop_vertex_indicator_active = prop_vertex_indicator_active; diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 5aff46f09..a76158d93 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -5104,6 +5104,20 @@ impl OpenCADStudio { Task::none() } + Message::PropSectionToggle(title) => { + let sections = &mut self.collapsed_property_sections; + if !sections.remove(&title) { + sections.insert(title); + } + // Keep already-built panels in other open documents in sync, + // rather than waiting for each one to rebuild on selection. + let sections = self.collapsed_property_sections.clone(); + for tab in &mut self.tabs { + tab.properties.collapsed_sections = sections.clone(); + } + Task::none() + } + Message::PropEditChoiceToggle => { let panel = &mut self.tabs[self.active_tab].properties; panel.edit_choice_open = !panel.edit_choice_open; diff --git a/src/ui/properties.rs b/src/ui/properties.rs index fb0660c52..7bbf8329b 100644 --- a/src/ui/properties.rs +++ b/src/ui/properties.rs @@ -447,6 +447,10 @@ pub struct PropertiesPanel { /// carried across panel rebuilds so the state survives edits and selection /// changes. pub expanded_groups: HashSet, + /// Property sections the user collapsed. This is a rendered copy of the + /// app-wide preference, allowing every open document panel to share it. + /// Empty by default, meaning every section is initially shown. + pub collapsed_sections: HashSet, /// Whether the editable-dropdown (block Name) option list is open. pub edit_choice_open: bool, /// Field key of the value row currently being edited, or `None`. Marked when @@ -485,6 +489,7 @@ impl Default for PropertiesPanel { prop_vertex: 0, prop_vertex_indicator_active: false, expanded_groups: HashSet::default(), + collapsed_sections: HashSet::default(), edit_choice_open: false, active_field: None, field_key_by_id: HashMap::default(), @@ -710,8 +715,32 @@ impl PropertiesPanel { // ── Section renderer ────────────────────────────────────────────────── fn render_section<'a>(&'a self, section: &'a PropSection) -> Element<'a, Message> { - // Section header - let hdr = container(text(§ion.title).size(10)) + let collapsed = self.collapsed_sections.contains(§ion.title); + let toggle = button(if collapsed { + crate::ui::icons::themed_arrow_right(10.0) + } else { + crate::ui::icons::themed_arrow_down(10.0) + }) + .on_press(Message::PropSectionToggle(section.title.clone())) + .style(button::text) + .padding([0, 3]); + + // The arrow intentionally sits at the far end of the header. The rest + // of the header responds to a double-click, leaving a single click on + // the arrow as the precise, discoverable toggle target. + let title = mouse_area( + container(text(§ion.title).size(10)) + .width(Length::Fill) + .padding([3, 8]), + ) + .on_double_click(Message::PropSectionToggle(section.title.clone())); + let hdr = container( + row![ + title, + toggle, + ] + .align_y(iced::Center), + ) .style(|theme: &Theme| { let palette = theme.palette(); container::Style { @@ -724,10 +753,12 @@ impl PropertiesPanel { ..Default::default() } }) - .width(Length::Fill) - .padding([3, 8]); + .width(Length::Fill); let mut col = column![hdr].spacing(0); + if collapsed { + return col.into(); + } // Consecutive " X / Y [/ Z]" text rows collapse // into one clickable summary row; clicking expands the components.