diff --git a/egui/src/containers/collapsing_header.rs b/egui/src/containers/collapsing_header.rs index 65f65049..d5032c66 100644 --- a/egui/src/containers/collapsing_header.rs +++ b/egui/src/containers/collapsing_header.rs @@ -140,6 +140,9 @@ pub struct CollapsingHeader { default_open: bool, id_source: Id, enabled: bool, + selectable: bool, + selected: bool, + show_background: bool, } impl CollapsingHeader { @@ -157,6 +160,9 @@ impl CollapsingHeader { default_open: false, id_source, enabled: true, + selectable: false, + selected: false, + show_background: false, } } @@ -188,6 +194,44 @@ impl CollapsingHeader { self.enabled = enabled; self } + + /// Can the `CollapsingHeader` be selected by clicking it? Default: `false`. + /// + pub fn selectable(mut self, selectable: bool) -> Self { + self.selectable = selectable; + self + } + + /// If you set this to 'true', the `CollapsingHeader` will be shown as selected. + /// + /// Example: + /// ``` + /// # let ui = &mut egui::Ui::__test(); + /// let mut selected = false; + /// let response = egui::CollapsingHeader::new("Select and open me") + /// .selectable(true) + /// .selected(selected) + /// .show(ui, |ui| ui.label("Content")); + /// if response.header_response.clicked() { + /// selected = true; + /// } + /// ``` + pub fn selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + /// Should the `CollapsingHeader` show a background behind it? Default: `false`. + /// + /// To show it behind all `CollapsingHeader` you can just use: + /// ``` + /// # let ui = &mut egui::Ui::__test(); + /// ui.visuals_mut().collapsing_header_frame = true; + /// ``` + pub fn show_background(mut self, show_background: bool) -> Self { + self.show_background = show_background; + self + } } struct Prepared { @@ -207,6 +251,9 @@ impl CollapsingHeader { default_open, id_source, enabled: _, + selectable: _, + selected: _, + show_background: _, } = self; label.text_style = label @@ -247,10 +294,16 @@ impl CollapsingHeader { header_response .widget_info(|| WidgetInfo::labeled(WidgetType::CollapsingHeader, &galley.text)); - let visuals = ui.style().interact(&header_response); - let text_color = visuals.text_color(); + let visuals = ui + .style() + .interact_selectable(&header_response, self.selected); + let text_color = ui + .style() + .visuals + .override_text_color + .unwrap_or_else(|| visuals.text_color()); - if ui.visuals().collapsing_header_frame { + if ui.visuals().collapsing_header_frame || self.show_background { ui.painter().add(Shape::Rect { rect: header_response.rect.expand(visuals.expansion), corner_radius: visuals.corner_radius, @@ -260,6 +313,16 @@ impl CollapsingHeader { }); } + if self.selected + || self.selectable && (header_response.hovered() || header_response.has_focus()) + { + let rect = rect.expand(visuals.expansion); + + let corner_radius = 2.0; + ui.painter() + .rect(rect, corner_radius, visuals.bg_fill, visuals.bg_stroke); + } + { let (mut icon_rect, _) = ui.spacing().icon_rectangles(header_response.rect); icon_rect.set_center(pos2( diff --git a/egui_demo_lib/src/apps/demo/misc_demo_window.rs b/egui_demo_lib/src/apps/demo/misc_demo_window.rs index da463a0a..4d762948 100644 --- a/egui_demo_lib/src/apps/demo/misc_demo_window.rs +++ b/egui_demo_lib/src/apps/demo/misc_demo_window.rs @@ -339,28 +339,53 @@ enum Action { #[derive(Clone, Default)] #[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] -struct Tree(Vec); +struct Tree(String, SubTree); impl Tree { pub fn demo() -> Self { - Self(vec![ - Tree(vec![Tree::default(); 4]), - Tree(vec![Tree(vec![Tree::default(); 2]); 3]), - ]) + Self( + String::from("root"), + SubTree(vec![ + SubTree(vec![SubTree::default(); 4]), + SubTree(vec![SubTree(vec![SubTree::default(); 2]); 3]), + ]), + ) } pub fn ui(&mut self, ui: &mut Ui) -> Action { - self.ui_impl(ui, 0, "root") + self.1.ui(ui, 0, "root", &mut self.0) } +} - fn ui_impl(&mut self, ui: &mut Ui, depth: usize, name: &str) -> Action { - CollapsingHeader::new(name) +#[derive(Clone, Default)] +#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))] +struct SubTree(Vec); + +impl SubTree { + pub fn ui( + &mut self, + ui: &mut Ui, + depth: usize, + name: &str, + selected_name: &mut String, + ) -> Action { + let response = CollapsingHeader::new(name) .default_open(depth < 1) - .show(ui, |ui| self.children_ui(ui, depth)) - .body_returned - .unwrap_or(Action::Keep) + .selectable(true) + .selected(selected_name.as_str() == name) + .show(ui, |ui| self.children_ui(ui, name, depth, selected_name)); + if response.header_response.clicked() { + *selected_name = name.to_string(); + } + response.body_returned.unwrap_or(Action::Keep) } - fn children_ui(&mut self, ui: &mut Ui, depth: usize) -> Action { + fn children_ui( + &mut self, + ui: &mut Ui, + parent_name: &str, + depth: usize, + selected_name: &mut String, + ) -> Action { if depth > 0 && ui .add(Button::new("delete").text_color(Color32::RED)) @@ -374,7 +399,13 @@ impl Tree { .into_iter() .enumerate() .filter_map(|(i, mut tree)| { - if tree.ui_impl(ui, depth + 1, &format!("child #{}", i)) == Action::Keep { + if tree.ui( + ui, + depth + 1, + &format!("{}/{}", parent_name, i), + selected_name, + ) == Action::Keep + { Some(tree) } else { None @@ -383,7 +414,7 @@ impl Tree { .collect(); if ui.button("+").clicked() { - self.0.push(Tree::default()); + self.0.push(SubTree::default()); } Action::Keep